Skip to content

Commit ac655f9

Browse files
[Chrome Next] Add AppHeader to dashboards listing page
1 parent 7101353 commit ac655f9

8 files changed

Lines changed: 157 additions & 29 deletions

File tree

src/platform/packages/shared/content-management/tabbed_table_list_view/src/tabbed_table_list_view.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface TableListBreadcrumb {
2121
export type TableListTabParentProps<T extends UserContentCommonSchema = UserContentCommonSchema> =
2222
Pick<TableListViewTableProps<T>, 'onFetchSuccess' | 'setPageDataTestSubject'> & {
2323
getBreadcrumbs?: (appId: string) => TableListBreadcrumb[];
24+
showCreateButton?: boolean;
2425
};
2526

2627
export interface TableListTab<T extends UserContentCommonSchema = UserContentCommonSchema> {
@@ -33,12 +34,14 @@ export interface TableListTab<T extends UserContentCommonSchema = UserContentCom
3334

3435
type TabbedTableListViewProps = Pick<
3536
TableListViewProps<UserContentCommonSchema>,
36-
'title' | 'description' | 'headingId' | 'children'
37+
'description' | 'headingId' | 'children'
3738
> & {
39+
title?: TableListViewProps<UserContentCommonSchema>['title'];
3840
tabs: TableListTab[];
3941
activeTabId: string;
4042
changeActiveTab: (id: string) => void;
4143
getBreadcrumbs?: TableListTabParentProps['getBreadcrumbs'];
44+
showCreateButton?: boolean;
4245
};
4346

4447
export const TabbedTableListView = ({
@@ -50,6 +53,7 @@ export const TabbedTableListView = ({
5053
activeTabId,
5154
changeActiveTab,
5255
getBreadcrumbs,
56+
showCreateButton,
5357
}: TabbedTableListViewProps) => {
5458
const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false);
5559
const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
@@ -71,17 +75,18 @@ export const TabbedTableListView = ({
7175
onFetchSuccess,
7276
setPageDataTestSubject,
7377
getBreadcrumbs,
78+
showCreateButton,
7479
});
7580
setTableList(newTableList);
7681
}
7782

7883
loadTableList();
79-
}, [activeTabId, tabs, getActiveTab, onFetchSuccess, getBreadcrumbs]);
84+
}, [activeTabId, tabs, getActiveTab, onFetchSuccess, getBreadcrumbs, showCreateButton]);
8085

8186
return (
8287
<KibanaPageTemplate panelled data-test-subj={pageDataTestSubject}>
8388
<KibanaPageTemplate.Header
84-
pageTitle={<span id={headingId}>{title}</span>}
89+
pageTitle={title ? <span id={headingId}>{title}</span> : undefined}
8590
description={description}
8691
data-test-subj="top-nav"
8792
tabs={tabs.map((tab) => ({
@@ -90,7 +95,9 @@ export const TabbedTableListView = ({
9095
label: tab.title,
9196
}))}
9297
/>
93-
<KibanaPageTemplate.Section aria-labelledby={hasInitialFetchReturned ? headingId : undefined}>
98+
<KibanaPageTemplate.Section
99+
aria-labelledby={hasInitialFetchReturned && title ? headingId : undefined}
100+
>
94101
{/* Any children passed to the component */}
95102
{children}
96103

src/platform/plugins/private/event_annotation_listing/public/plugin.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import type { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
11+
import { firstValueFrom } from 'rxjs';
1112
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
1213
import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
1314
import { Storage } from '@kbn/kibana-utils-plugin/public';
@@ -101,7 +102,35 @@ export class EventAnnotationListingPlugin
101102
},
102103
};
103104
dependencies.visualizations.listingViewRegistry.add(annotationGroupsTabConfig);
104-
dependencies.dashboard.registerListingPageTab(annotationGroupsTabConfig);
105+
dependencies.dashboard.registerListingPageTab({
106+
...annotationGroupsTabConfig,
107+
createAction: async () => {
108+
const [coreStart, pluginsStart] = await core.getStartServices();
109+
const currentApp = await firstValueFrom(coreStart.application.currentAppId$);
110+
if (!currentApp) return;
111+
const stateTransfer = pluginsStart.embeddable.getStateTransfer();
112+
const breadcrumbs = [
113+
{
114+
text: stateTransfer.getAppNameFromId(currentApp) ?? currentApp,
115+
href: coreStart.application.getUrlForApp(currentApp),
116+
},
117+
{
118+
text: tabTitle,
119+
href: coreStart.application.getUrlForApp(currentApp, {
120+
path: window.location.hash,
121+
}),
122+
},
123+
];
124+
await stateTransfer.navigateToEditor('lens', {
125+
path: '',
126+
state: {
127+
originatingApp: currentApp,
128+
originatingPath: window.location.hash,
129+
breadcrumbs,
130+
},
131+
});
132+
},
133+
});
105134
}
106135

107136
public start(core: CoreStart, plugins: object): void {

src/platform/plugins/shared/dashboard/public/dashboard_listing/dashboard_listing.test.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ jest.mock('@kbn/content-management-tabbed-table-list-view', () => ({
2727
TabbedTableListView: jest.fn().mockReturnValue(null),
2828
}));
2929

30+
jest.mock('@kbn/app-header', () => ({
31+
__esModule: true,
32+
AppHeader: () => null,
33+
}));
34+
3035
const renderDashboardListing = (
3136
props: Partial<DashboardListingProps> = {},
3237
{ initialEntries = ['/list'] }: { initialEntries?: string[] } = {}
@@ -58,7 +63,6 @@ test('renders TabbedTableListView with correct title and dashboards tab', () =>
5863
expect(mockTabbedTableListView).toHaveBeenCalledTimes(1);
5964
const props = mockTabbedTableListView.mock.calls[0][0];
6065
expect(props).toMatchObject({
61-
title: 'Dashboards',
6266
headingId: 'dashboardListingHeading',
6367
});
6468
expect(props.tabs[0]).toMatchObject({ id: 'dashboards', title: 'Dashboards' });

src/platform/plugins/shared/dashboard/public/dashboard_listing/dashboard_listing.tsx

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
1717
import { QueryClientProvider } from '@kbn/react-query';
1818
import type { EmbeddableEditorBreadcrumb } from '@kbn/embeddable-plugin/public';
1919

20+
import { AppHeader } from '@kbn/app-header';
21+
import type { AppMenuConfig } from '@kbn/core-chrome-app-menu-components';
2022
import { coreServices } from '../services/kibana_services';
2123
import { dashboardQueryClient } from '../services/dashboard_query_client';
2224
import { DASHBOARD_APP_ID, LANDING_PAGE_PATH } from '../../common/page_bundle_constants';
2325
import { getDashboardListingTabs } from './get_dashboard_listing_tabs';
24-
import type { DashboardListingProps } from './types';
26+
import type { DashboardListingProps, DashboardListingTab } from './types';
2527

2628
export const DashboardListing = ({
2729
children,
@@ -86,19 +88,63 @@ export const DashboardListing = ({
8688
[tabs, activeTabId]
8789
);
8890

91+
const appMenu: AppMenuConfig = useMemo(() => {
92+
const tabsByIdMap = new Map((tabs as DashboardListingTab[]).map((tab) => [tab.id, tab]));
93+
return {
94+
primaryActionItem: {
95+
id: 'create',
96+
iconType: 'plus',
97+
label: i18n.translate('dashboard.listing.createButtonLabel', {
98+
defaultMessage: 'Create',
99+
}),
100+
popoverWidth: 180,
101+
items: [
102+
{
103+
id: 'createDashboard',
104+
order: 1,
105+
label: 'Dashboard',
106+
iconType: 'productDashboard',
107+
testId: 'createDashboardButton',
108+
run: () => tabsByIdMap.get('dashboards')?.createAction?.(),
109+
},
110+
{
111+
id: 'createVisualization',
112+
order: 2,
113+
label: 'Visualization',
114+
iconType: 'chartBarVertical',
115+
testId: 'createVisualizationButton',
116+
run: () => tabsByIdMap.get('visualizations')?.createAction?.(),
117+
},
118+
{
119+
id: 'createAnnotation',
120+
order: 3,
121+
label: 'Annotation',
122+
iconType: 'flag',
123+
testId: 'createAnnotationButton',
124+
run: () => tabsByIdMap.get('annotations')?.createAction?.(),
125+
},
126+
],
127+
},
128+
};
129+
}, [tabs]);
130+
89131
return (
90132
<I18nProvider>
91133
<QueryClientProvider client={dashboardQueryClient}>
92134
{children}
93-
<TabbedTableListView
94-
headingId="dashboardListingHeading"
135+
<AppHeader
95136
title={i18n.translate('dashboard.listing.title', {
96137
defaultMessage: 'Dashboards',
97138
})}
139+
menu={appMenu}
140+
/>
141+
<TabbedTableListView
142+
headingId="dashboardListingHeading"
98143
getBreadcrumbs={getBreadcrumbs}
99144
tabs={tabs}
100145
activeTabId={activeTabId}
101146
changeActiveTab={changeActiveTab}
147+
showCreateButton={false}
102148
/>
103149
</QueryClientProvider>
104150
</I18nProvider>

src/platform/plugins/shared/dashboard/public/dashboard_listing/get_dashboard_listing_tabs.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99

1010
import React, { useMemo } from 'react';
1111
import { i18n } from '@kbn/i18n';
12-
import type {
13-
TableListTab,
14-
TableListTabParentProps,
15-
} from '@kbn/content-management-tabbed-table-list-view';
12+
import type { TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view';
1613
import {
1714
TableListViewTable,
1815
TableListViewKibanaProvider,
@@ -29,7 +26,13 @@ import {
2926
} from '../services/kibana_services';
3027
import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
3128
import { useDashboardListingTable } from './hooks/use_dashboard_listing_table';
32-
import type { DashboardListingProps, DashboardSavedObjectUserContent } from './types';
29+
import { confirmCreateWithUnsaved } from './confirm_overlays';
30+
import { getDashboardBackupService } from '../services/dashboard_api_services';
31+
import type {
32+
DashboardListingProps,
33+
DashboardListingTab,
34+
DashboardSavedObjectUserContent,
35+
} from './types';
3336

3437
type GetDashboardListingTabsParams = Pick<
3538
DashboardListingProps,
@@ -64,6 +67,7 @@ const DashboardsTabContent = ({
6467
getDashboardUrl,
6568
useSessionStorageIntegration,
6669
initialFilter,
70+
showCreateDashboardButton: parentProps.showCreateButton,
6771
});
6872

6973
const dashboardFavoritesClient = useMemo(() => {
@@ -101,22 +105,32 @@ export const getDashboardListingTabs = ({
101105
useSessionStorageIntegration,
102106
initialFilter,
103107
getTabs,
104-
}: GetDashboardListingTabsParams): TableListTab<DashboardSavedObjectUserContent>[] => {
108+
}: GetDashboardListingTabsParams): DashboardListingTab[] => {
105109
const commonProps = {
106110
goToDashboard,
107111
getDashboardUrl,
108112
useSessionStorageIntegration,
109113
initialFilter,
110114
};
111115

112-
const dashboardsTab: TableListTab<DashboardSavedObjectUserContent> = {
116+
const dashboardsTab: DashboardListingTab = {
113117
title: i18n.translate('dashboard.listing.tabs.dashboards.title', {
114118
defaultMessage: 'Dashboards',
115119
}),
116120
id: 'dashboards',
117121
getTableList: (parentProps) => (
118122
<DashboardsTabContent {...commonProps} parentProps={parentProps} />
119123
),
124+
createAction: () => {
125+
if (useSessionStorageIntegration && getDashboardBackupService().dashboardHasUnsavedEdits()) {
126+
confirmCreateWithUnsaved(() => {
127+
getDashboardBackupService().clearState();
128+
goToDashboard();
129+
}, goToDashboard);
130+
return;
131+
}
132+
goToDashboard();
133+
},
120134
};
121135

122136
// Additional tabs (e.g., visualizations and annotation groups)

src/platform/plugins/shared/dashboard/public/dashboard_listing/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ import type { SavedObjectAccessControl } from '@kbn/core-saved-objects-common';
1414
import type { TableListTab } from '@kbn/content-management-tabbed-table-list-view';
1515
import type { AppDeepLinkLocations } from '@kbn/core/public';
1616

17-
/** Tab interface with optional deep link support. */
17+
/** Tab interface with optional deep link and create action support. */
1818
export type DashboardListingTab = TableListTab & {
1919
deepLink?: {
20-
/** Title to display in global search results */
2120
title: string;
22-
/** Where this deep link should be visible. */
2321
visibleIn?: AppDeepLinkLocations[];
2422
};
23+
createAction?: () => void;
2524
};
2625

2726
export type DashboardListingProps = PropsWithChildren<{

src/platform/plugins/shared/visualization_listing/public/components/visualization_table_list.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const VisualizationTableList = ({
6060
savedObjectsTagging,
6161
parentProps,
6262
}: VisualizationTableListProps) => {
63-
const { getBreadcrumbs, onFetchSuccess, setPageDataTestSubject } = parentProps;
63+
const { getBreadcrumbs, onFetchSuccess, setPageDataTestSubject, showCreateButton } = parentProps;
6464
const euiThemeContext = useEuiTheme();
6565
const tableStyles = useMemo(
6666
() => getVisualizationListingTableStyles(euiThemeContext),
@@ -74,14 +74,15 @@ export const VisualizationTableList = ({
7474
const visualizedUserContent = useRef<VisualizeUserContent[]>();
7575
const closeNewVisModal = useRef(() => {});
7676

77-
const createNewVis = useCallback(async () => {
78-
const currentApp = await firstValueFrom(core.application.currentAppId$);
79-
const breadcrumbs = currentApp ? getBreadcrumbs?.(currentApp) : undefined;
80-
closeNewVisModal.current = visualizations.showNewVisModal({
81-
originatingApp: currentApp,
82-
originatingPath: window.location.hash,
83-
breadcrumbs,
84-
outsideVisualizeApp: currentApp !== VISUALIZE_APP_NAME,
77+
const createNewVis = useCallback(() => {
78+
firstValueFrom(core.application.currentAppId$).then((currentApp) => {
79+
const breadcrumbs = currentApp ? getBreadcrumbs?.(currentApp) : undefined;
80+
closeNewVisModal.current = visualizations.showNewVisModal({
81+
originatingApp: currentApp,
82+
originatingPath: window.location.hash,
83+
breadcrumbs,
84+
outsideVisualizeApp: currentApp !== VISUALIZE_APP_NAME,
85+
});
8586
});
8687
}, [visualizations, core.application, getBreadcrumbs]);
8788

@@ -244,7 +245,7 @@ export const VisualizationTableList = ({
244245
customValidators: contentEditorValidators,
245246
}}
246247
emptyPrompt={noItemsFragment}
247-
createItem={createNewVis}
248+
createItem={showCreateButton === false ? undefined : createNewVis}
248249
customTableColumn={getCustomColumn()}
249250
customSortingOptions={getCustomSortingOptions()}
250251
initialPageSize={initialPageSize}

src/platform/plugins/shared/visualization_listing/public/plugin.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
*/
99

1010
import type { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
11+
import { firstValueFrom } from 'rxjs';
1112
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
1213
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
1314
import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
1415
import type { DashboardSetup, DashboardListingTab } from '@kbn/dashboard-plugin/public';
1516
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
1617
import type { TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view';
1718
import { i18n } from '@kbn/i18n';
19+
import { VISUALIZE_APP_NAME } from '@kbn/visualizations-common';
1820
import type { VisualizationListingPageServices } from './get_table_list';
1921

2022
export interface VisualizationListingStartDependencies {
@@ -72,6 +74,32 @@ export class VisualizationListingPlugin
7274
}),
7375
visibleIn: ['globalSearch'],
7476
},
77+
createAction: async () => {
78+
const [coreStart, pluginsStart] = await core.getStartServices();
79+
const currentApp = await firstValueFrom(coreStart.application.currentAppId$);
80+
const breadcrumbs = currentApp
81+
? [
82+
{
83+
text:
84+
pluginsStart.embeddable.getStateTransfer().getAppNameFromId(currentApp) ??
85+
currentApp,
86+
href: coreStart.application.getUrlForApp(currentApp),
87+
},
88+
{
89+
text: tabTitle,
90+
href: coreStart.application.getUrlForApp(currentApp, {
91+
path: window.location.hash,
92+
}),
93+
},
94+
]
95+
: undefined;
96+
pluginsStart.visualizations.showNewVisModal({
97+
originatingApp: currentApp,
98+
originatingPath: window.location.hash,
99+
outsideVisualizeApp: currentApp !== VISUALIZE_APP_NAME,
100+
breadcrumbs,
101+
});
102+
},
75103
};
76104

77105
if (dependencies.dashboard) {

0 commit comments

Comments
 (0)