From 46afb96cdf57b53683457c4dde98bbf6ae155bfe Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Tue, 23 Jul 2024 00:01:42 +0800 Subject: [PATCH] [navigation-next] feat: add recent works in new homepage (#7237) * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * Changeset file for PR #7237 created/updated * feat: remove the euispacer Signed-off-by: SuZhou-Joe * feat: update empty state Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update i18n key Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> --- changelogs/fragments/7237.yml | 2 + src/core/public/chrome/index.ts | 7 +- .../recently_accessed_service.ts | 19 +- src/core/public/chrome/ui/header/index.ts | 1 + src/core/public/chrome/ui/index.ts | 1 + src/core/public/index.ts | 2 + .../use/use_saved_dashboard_instance.test.ts | 1 + .../utils/use/use_saved_dashboard_instance.ts | 3 +- .../view_components/utils/use_search.ts | 5 +- .../discover/public/saved_searches/types.ts | 9 +- .../home/public/application/home_render.tsx | 14 +- .../saved_object/helpers/save_saved_object.ts | 5 +- .../opensearch_dashboards.json | 3 +- .../management_section/recent_work.test.tsx | 98 +++++++ .../public/management_section/recent_work.tsx | 276 ++++++++++++++++++ .../saved_objects_management/public/plugin.ts | 25 +- .../utils/use/use_saved_vis_builder_vis.ts | 7 + .../public/saved_visualizations/_saved_vis.ts | 1 + .../utils/use/use_saved_vis_instance.test.ts | 2 + .../utils/use/use_saved_vis_instance.ts | 8 + 20 files changed, 479 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/7237.yml create mode 100644 src/plugins/saved_objects_management/public/management_section/recent_work.test.tsx create mode 100644 src/plugins/saved_objects_management/public/management_section/recent_work.tsx diff --git a/changelogs/fragments/7237.yml b/changelogs/fragments/7237.yml new file mode 100644 index 00000000000..28da091c7e1 --- /dev/null +++ b/changelogs/fragments/7237.yml @@ -0,0 +1,2 @@ +feat: +- [navigation-next] add recent works in new homepage ([#7237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7237)) \ No newline at end of file diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index 02100ce4f63..d37f14dc9e8 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -44,7 +44,12 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, } from './ui/header/header_help_menu'; -export { NavType, RightNavigationButton, RightNavigationButtonProps } from './ui'; +export { + NavType, + RightNavigationButton, + RightNavigationButtonProps, + createRecentNavLink, +} from './ui'; export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields } from './nav_links'; export { ChromeRecentlyAccessed, diff --git a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts index e111d71d25d..99ca6151385 100644 --- a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts +++ b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts @@ -41,6 +41,10 @@ export interface ChromeRecentlyAccessedHistoryItem { label: string; id: string; workspaceId?: string; + meta?: { + type?: string; + lastAccessedTime?: number; + }; } interface StartDeps { @@ -59,7 +63,12 @@ export class RecentlyAccessedService { return { /** Adds a new item to the history. */ - add: (link: string, label: string, id: string) => { + add: ( + link: string, + label: string, + id: string, + meta?: ChromeRecentlyAccessedHistoryItem['meta'] + ) => { const currentWorkspaceId = workspaces.currentWorkspaceId$.getValue(); history.add({ @@ -67,6 +76,7 @@ export class RecentlyAccessedService { label, id, ...(currentWorkspaceId && { workspaceId: currentWorkspaceId }), + ...(meta && { meta: { lastAccessedTime: Date.now(), ...meta } }), }); }, @@ -96,7 +106,12 @@ export interface ChromeRecentlyAccessed { * @param label the label to display in the UI * @param id a unique string used to de-duplicate the recently accessed list. */ - add(link: string, label: string, id: string): void; + add( + link: string, + label: string, + id: string, + meta?: ChromeRecentlyAccessedHistoryItem['meta'] + ): void; /** * Gets an Array of the current recently accessed history. diff --git a/src/core/public/chrome/ui/header/index.ts b/src/core/public/chrome/ui/header/index.ts index 811eca0cad8..a43a3e7470d 100644 --- a/src/core/public/chrome/ui/header/index.ts +++ b/src/core/public/chrome/ui/header/index.ts @@ -38,3 +38,4 @@ export { ChromeHelpExtensionMenuGitHubLink, } from './header_help_menu'; export { RightNavigationButton, RightNavigationButtonProps } from './right_navigation_button'; +export { createRecentNavLink } from './nav_link'; diff --git a/src/core/public/chrome/ui/index.ts b/src/core/public/chrome/ui/index.ts index 11f0c0302df..963f5457132 100644 --- a/src/core/public/chrome/ui/index.ts +++ b/src/core/public/chrome/ui/index.ts @@ -39,4 +39,5 @@ export { NavType, RightNavigationButton, RightNavigationButtonProps, + createRecentNavLink, } from './header'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 8c387361a9c..9da3aed8973 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -76,6 +76,7 @@ import { PersistedLog, NavGroupItemInMap, fulfillRegistrationLinksToChromeNavLinks, + createRecentNavLink, } from './chrome'; import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors'; import { HttpSetup, HttpStart } from './http'; @@ -379,6 +380,7 @@ export { PersistedLog, NavGroupItemInMap, fulfillRegistrationLinksToChromeNavLinks, + createRecentNavLink, }; export { __osdBootstrap__ } from './osd_bootstrap'; diff --git a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.test.ts b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.test.ts index b7b69a39de5..2b1ab3e1c86 100644 --- a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.test.ts +++ b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.test.ts @@ -38,6 +38,7 @@ describe('useSavedDashboardInstance', () => { getFilters: () => dashboardAppStateStub.filters, optionsJSON: JSON.stringify(dashboardAppStateStub.options), getFullPath: () => `/${dashboardIdFromUrl}`, + getOpenSearchType: () => 'dashboard', }, } as unknown) as SavedObjectDashboard; dashboard = new Dashboard(convertToSerializedDashboard(savedDashboardInstance)); diff --git a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts index 7cc0a46b2a5..fedc353f3e2 100644 --- a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts +++ b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts @@ -129,7 +129,8 @@ export const useSavedDashboardInstance = ({ chrome.recentlyAccessed.add( savedDashboard.getFullPath(), savedDashboard.title, - dashboardIdFromUrl + dashboardIdFromUrl, + { type: savedDashboard.getOpenSearchType() } ); setSavedDashboardInstance(dashboardInstance); } catch (error: any) { diff --git a/src/plugins/discover/public/application/view_components/utils/use_search.ts b/src/plugins/discover/public/application/view_components/utils/use_search.ts index 4de9e987bd5..8c2ace81b04 100644 --- a/src/plugins/discover/public/application/view_components/utils/use_search.ts +++ b/src/plugins/discover/public/application/view_components/utils/use_search.ts @@ -309,7 +309,10 @@ export const useSearch = (services: DiscoverViewServices) => { chrome.recentlyAccessed.add( savedSearchInstance.getFullPath(), savedSearchInstance.title, - savedSearchInstance.id + savedSearchInstance.id, + { + type: savedSearchInstance.getOpenSearchType(), + } ); } })(); diff --git a/src/plugins/discover/public/saved_searches/types.ts b/src/plugins/discover/public/saved_searches/types.ts index 73cb25774c4..f013eab77ed 100644 --- a/src/plugins/discover/public/saved_searches/types.ts +++ b/src/plugins/discover/public/saved_searches/types.ts @@ -36,7 +36,14 @@ export type SortOrder = [string, SortDirection]; export interface SavedSearch extends Pick< SavedObject, - 'id' | 'title' | 'copyOnSave' | 'destroy' | 'lastSavedTitle' | 'save' | 'getFullPath' + | 'id' + | 'title' + | 'copyOnSave' + | 'destroy' + | 'lastSavedTitle' + | 'save' + | 'getFullPath' + | 'getOpenSearchType' > { searchSource: ISearchSource; // This is optional in SavedObject, but required for SavedSearch description?: string; diff --git a/src/plugins/home/public/application/home_render.tsx b/src/plugins/home/public/application/home_render.tsx index 757475dd7f4..5d0497b7c39 100644 --- a/src/plugins/home/public/application/home_render.tsx +++ b/src/plugins/home/public/application/home_render.tsx @@ -31,7 +31,19 @@ export const setupHome = (contentManagement: ContentManagementPluginSetup) => { order: 2000, title: 'Recently viewed', kind: 'custom', - render: () => <>, + render: (contents) => { + return ( + <> + {contents.map((content) => { + if (content.kind === 'custom') { + return content.render(); + } + + return null; + })} + + ); + }, }, { id: SECTIONS.GET_STARTED, diff --git a/src/plugins/saved_objects/public/saved_object/helpers/save_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/save_saved_object.ts index 01be4f97591..2e75ff3dd44 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/save_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/save_saved_object.ts @@ -123,7 +123,10 @@ export async function saveSavedObject( chrome.recentlyAccessed.add( savedObject.getFullPath(), savedObject.title, - String(savedObject.id) + String(savedObject.id), + { + type: savedObject.getOpenSearchType(), + } ); } savedObject.isSaving = false; diff --git a/src/plugins/saved_objects_management/opensearch_dashboards.json b/src/plugins/saved_objects_management/opensearch_dashboards.json index 225fe67e9f5..2802eb04b3d 100644 --- a/src/plugins/saved_objects_management/opensearch_dashboards.json +++ b/src/plugins/saved_objects_management/opensearch_dashboards.json @@ -12,7 +12,8 @@ "visBuilder", "visAugmenter", "dataSource", - "dataSourceManagement" + "dataSourceManagement", + "contentManagement" ], "extraPublicDirs": ["public/lib"], "requiredBundles": ["opensearchDashboardsReact", "home"] diff --git a/src/plugins/saved_objects_management/public/management_section/recent_work.test.tsx b/src/plugins/saved_objects_management/public/management_section/recent_work.test.tsx new file mode 100644 index 00000000000..6441ce280f6 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/recent_work.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { fireEvent, render } from '@testing-library/react'; +import { RecentWork } from './recent_work'; +import { coreMock } from '../../../../core/public/mocks'; +import { ChromeRecentlyAccessedHistoryItem } from 'opensearch-dashboards/public'; +import { SavedObjectWithMetadata } from '../types'; + +const mockedRecentItems: ChromeRecentlyAccessedHistoryItem[] = [ + { + link: '/app/visualize', + label: 'visualize', + id: 'visualize', + meta: { + type: 'visualize', + }, + }, + { + link: '/app/dashboard', + label: 'dashboard', + id: 'dashboard-in-workspace', + workspaceId: 'workspace-id', + meta: { + type: 'dashboard', + }, + }, +]; + +const savedObjectsFromServer: SavedObjectWithMetadata[] = [ + { + type: 'visualize', + id: 'visualize', + attributes: {}, + references: [], + updated_at: '2024-07-20T00:00:00.000Z', + meta: {}, + }, + { + type: 'dashboard', + id: 'dashboard-in-workspace', + attributes: {}, + references: [], + updated_at: '2024-07-20T00:00:01.000Z', + meta: {}, + }, +]; + +const getStartMockForRecentWork = () => { + const coreStartMock = coreMock.createStart(); + coreStartMock.chrome.recentlyAccessed.get$.mockReturnValue(new BehaviorSubject([])); + coreStartMock.chrome.navLinks.getNavLinks$.mockReturnValue(new BehaviorSubject([])); + return coreStartMock; +}; + +describe('', () => { + it('render with emty recent work', async () => { + const { findByText } = render(); + await findByText('No recent work'); + }); + + it('render with recent works', async () => { + const coreStartMock = getStartMockForRecentWork(); + coreStartMock.http.get.mockImplementation((url) => { + if (typeof url === 'string') { + if ((url as string).includes(mockedRecentItems[0].id)) { + return Promise.resolve(savedObjectsFromServer[0]); + } else { + return Promise.resolve(savedObjectsFromServer[1]); + } + } + + return Promise.reject({}); + }); + + coreStartMock.chrome.recentlyAccessed.get$.mockReturnValue( + new BehaviorSubject(mockedRecentItems) + ); + + const { findAllByTestId, getByTestId } = render(); + const allCards = await findAllByTestId('recentlyCard'); + expect(allCards.length).toBe(2); + expect(allCards[0].querySelector('.euiCard__titleAnchor')?.textContent).toEqual( + mockedRecentItems[0].label + ); + + // click the filter button + fireEvent.click(getByTestId('filterButton-recently%20updated')); + const allCardsAfterSort = await findAllByTestId('recentlyCard'); + expect(allCardsAfterSort[0].querySelector('.euiCard__titleAnchor')?.textContent).toEqual( + mockedRecentItems[1].label + ); + }); +}); diff --git a/src/plugins/saved_objects_management/public/management_section/recent_work.tsx b/src/plugins/saved_objects_management/public/management_section/recent_work.tsx new file mode 100644 index 00000000000..2e5b45e2cd6 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/recent_work.tsx @@ -0,0 +1,276 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useMemo, useState, useEffect, useRef } from 'react'; +import moment from 'moment'; +import { + EuiFlexItem, + EuiCard, + EuiPanel, + EuiSpacer, + EuiFlexGroup, + EuiTitle, + EuiFilterGroup, + EuiFilterButton, + EuiComboBox, + EuiIcon, + EuiEmptyPrompt, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { + ChromeRecentlyAccessedHistoryItem, + CoreStart, + SavedObject, +} from 'opensearch-dashboards/public'; +import { useObservable } from 'react-use'; +import { SavedObjectWithMetadata } from 'src/plugins/saved_objects_management/common'; +import { createRecentNavLink } from '../../../../core/public'; + +const allOption = i18n.translate('savedObjectsManagement.recentWorkSection.all.items', { + defaultMessage: 'all items', +}); + +const recentlyViewed = i18n.translate('savedObjectsManagement.recentWorkSection.recentlyViewed', { + defaultMessage: 'recently viewed', +}); + +const recentlyUpdated = i18n.translate('savedObjectsManagement.recentWorkSection.recentlyUpdated', { + defaultMessage: 'recently updated', +}); + +const sortKeyMap = { + [recentlyViewed]: 'lastAccessedTime', + [recentlyUpdated]: 'updatedAt', +} as const; + +type KeyOf = keyof T; + +function sortBy(key: KeyOf) { + return (a: T, b: T): number => (a[key] > b[key] ? -1 : b[key] > a[key] ? 1 : 0); +} + +type DetailedRecentlyAccessedItem = ChromeRecentlyAccessedHistoryItem & + SavedObjectWithMetadata & + ChromeRecentlyAccessedHistoryItem['meta'] & { + updatedAt: number; + workspaceName?: string; + }; + +const bulkGetDetail = ( + savedObjects: Array>, + http: CoreStart['http'] +) => { + return Promise.all( + savedObjects.map((obj) => + http + .get( + `/api/opensearch-dashboards/management/saved_objects/${encodeURIComponent( + obj.type + )}/${encodeURIComponent(obj.id)}` + ) + .catch((error) => ({ + id: obj.id, + type: obj.type, + error, + attributes: {}, + references: [], + meta: {}, + updated_at: '', + })) + ) + ); +}; + +const widthForTypeSelector = 220; +const widthForRightMargin = 4; +const MAX_ITEMS_DISPLAY = 6; + +export const RecentWork = (props: { core: CoreStart; workspaceEnabled?: boolean }) => { + const { core, workspaceEnabled } = props; + const recently$Ref = useRef(core.chrome.recentlyAccessed.get$()); + const recentAccessed = useObservable(recently$Ref.current, []); + const workspaceList = useObservable(core.workspaces.workspaceList$, []); + const [selectedType, setSelectedType] = useState(allOption); + const [selectedSort, setSelectedSort] = useState(recentlyViewed); + const [detailedSavedObjects, setDetailedSavedObjects] = useState( + [] + ); + + const allOptions = useMemo(() => { + const options: string[] = [allOption]; + detailedSavedObjects + .filter((item) => !item.error) + .forEach((recentAccessItem: ChromeRecentlyAccessedHistoryItem) => { + if (recentAccessItem.meta?.type && options.indexOf(recentAccessItem.meta.type) === -1) { + options.push(recentAccessItem.meta.type); + } + }); + return options.map((option: string) => ({ label: option, value: option })); + }, [detailedSavedObjects]); + + const itemsForDisplay = useMemo(() => { + const sortedResult = [...detailedSavedObjects] + .filter((item) => !item.error) + .sort(sortBy(sortKeyMap[selectedSort])); + return sortedResult.filter((item: SavedObject & ChromeRecentlyAccessedHistoryItem) => { + if (selectedType === allOption) return true; + return item.type === selectedType; + }); + }, [detailedSavedObjects, selectedSort, selectedType]); + + useEffect(() => { + const savedObjects = recentAccessed + .filter((item) => item.meta?.type) + .map((item) => ({ + type: item.meta?.type || '', + id: item.id, + })); + + if (savedObjects.length) { + bulkGetDetail(savedObjects, core.http).then((res) => { + const formatDetailedSavedObjects = res.map((obj) => { + const recentAccessItem = recentAccessed.find( + (item) => item.id === obj.id + ) as ChromeRecentlyAccessedHistoryItem; + + const findWorkspace = workspaceList.find( + (workspace) => workspace.id === recentAccessItem.workspaceId + ); + + return { + ...recentAccessItem, + ...obj, + ...recentAccessItem.meta, + updatedAt: moment(obj?.updated_at).valueOf(), + workspaceName: findWorkspace?.name, + }; + }); + setDetailedSavedObjects(formatDetailedSavedObjects); + }); + } + }, [core.savedObjects.client, recentAccessed, core.http, workspaceList]); + + return ( + + + + +
+ {i18n.translate('savedObjectsManagement.recentWorkSection.title', { + defaultMessage: 'Assets', + })} +
+
+
+ + + + + {[recentlyViewed, recentlyUpdated].map((item) => ( + setSelectedSort(item)} + data-test-subj={`filterButton-${encodeURIComponent(item)}`} + > + {item} + + ))} + + + + setSelectedType(options[0].value || '')} + selectedOptions={[{ label: selectedType, value: selectedType }]} + /> + + + +
+ {itemsForDisplay.length ? ( + + {Array.from({ length: MAX_ITEMS_DISPLAY }).map((item, itemIndexInRow) => { + const recentAccessItem = itemsForDisplay[itemIndexInRow]; + let content = null; + if (recentAccessItem) { + const navLinks = core.chrome.navLinks.getAll(); + const recentNavLink = createRecentNavLink( + recentAccessItem, + navLinks, + core.http.basePath, + core.application.navigateToUrl + ); + content = ( + +
+ + {recentAccessItem.type} +
+ +
+ {selectedSort === recentlyViewed + ? i18n.translate('savedObjectsManagement.recentWorkSection.viewedAt', { + defaultMessage: 'Viewed', + }) + : i18n.translate('savedObjectsManagement.recentWorkSection.updatedAt', { + defaultMessage: 'Updated', + })} + :{' '} + + {selectedSort === recentlyViewed + ? moment(recentAccessItem?.lastAccessedTime).fromNow() + : moment(recentAccessItem?.updatedAt).fromNow()} + +
+ {workspaceEnabled && ( +
+ {i18n.translate('savedObjectsManagement.recentWorkSection.workspace', { + defaultMessage: 'Workspace', + })} + : {recentAccessItem.workspaceName || 'N/A'} +
+ )} + + } + onClick={recentNavLink.onClick} + /> + ); + } + return ( + {content} + ); + })} +
+ ) : ( + + {i18n.translate('savedObjectsManagement.recentWorkSection.empty.title', { + defaultMessage: 'No recent work', + })} + + } + /> + )} +
+ ); +}; diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 5815874390f..1247b56b555 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -28,10 +28,12 @@ * under the License. */ +import React from 'react'; import { i18n } from '@osd/i18n'; import { AppMountParameters, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; +import { ContentManagementPluginStart } from 'src/plugins/content_management/public'; import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; import { VisBuilderStart } from '../../vis_builder/public'; import { ManagementSetup } from '../../management/public'; @@ -62,6 +64,8 @@ import { import { registerServices } from './register_services'; import { bootstrap } from './ui_actions_bootstrap'; import { DEFAULT_NAV_GROUPS, DEFAULT_APP_CATEGORIES } from '../../../core/public'; +import { RecentWork } from './management_section/recent_work'; +import { HOME_CONTENT_AREAS } from '../../../plugins/home/public'; export interface SavedObjectsManagementPluginSetup { actions: SavedObjectsManagementActionServiceSetup; @@ -93,6 +97,7 @@ export interface StartDependencies { discover?: DiscoverStart; visBuilder?: VisBuilderStart; uiActions: UiActionsStart; + contentManagement?: ContentManagementPluginStart; } export class SavedObjectsManagementPlugin @@ -239,10 +244,28 @@ export class SavedObjectsManagementPlugin }; } - public start(core: CoreStart, { data, uiActions }: StartDependencies) { + public start(core: CoreStart, { data, uiActions, contentManagement }: StartDependencies) { const actionStart = this.actionService.start(); const columnStart = this.columnService.start(); const namespaceStart = this.namespaceService.start(); + const workspaceEnabled = core.application.capabilities.workspaces.enabled; + + contentManagement?.registerContentProvider({ + id: 'recent', + getContent: () => { + return { + order: 1, + id: 'recent', + kind: 'custom', + render: () => + React.createElement(RecentWork, { + core, + workspaceEnabled, + }), + }; + }, + getTargetArea: () => HOME_CONTENT_AREAS.RECENTLY_VIEWED, + }); return { actions: actionStart, diff --git a/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts b/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts index 3ec69902c9c..8f67b8d2358 100644 --- a/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts +++ b/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts @@ -74,6 +74,13 @@ export const useSavedVisBuilderVis = (visualizationIdFromUrl: string | undefined data.query.filterManager.setAppFilters(actualFilters); data.query.queryString.setQuery(query); + chrome.recentlyAccessed.add( + savedVisBuilderVis.getFullPath(), + title, + savedVisBuilderVis.id, + { type: savedVisBuilderVis.getOpenSearchType() } + ); + dispatch(setUIStateState(state.ui)); dispatch(setStyleState(state.style)); dispatch(setVisualizationState(state.visualization)); diff --git a/src/plugins/vis_builder/public/saved_visualizations/_saved_vis.ts b/src/plugins/vis_builder/public/saved_visualizations/_saved_vis.ts index 021af777df1..18a8a472c7f 100644 --- a/src/plugins/vis_builder/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/vis_builder/public/saved_visualizations/_saved_vis.ts @@ -51,6 +51,7 @@ export function createSavedVisBuilderVisClass(services: SavedObjectOpenSearchDas }); this.showInRecentlyAccessed = true; this.getFullPath = () => `/app/${PLUGIN_ID}${EDIT_PATH}/${this.id}`; + this.getOpenSearchType = () => VISBUILDER_SAVED_OBJECT; } } diff --git a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts index 46d616d0673..0dbbf182df2 100644 --- a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts +++ b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts @@ -52,6 +52,8 @@ const mockSavedVisInstance = { id: savedVisId, title: 'Test Vis', destroy: mockSavedVisDestroy, + getOpenSearchType: () => 'visualization', + getFullPath: () => '/', }, vis: { type: {}, diff --git a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts index c80b2b1afd5..2438f26a648 100644 --- a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts +++ b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts @@ -107,6 +107,14 @@ export const useSavedVisInstance = ( if (savedVis.id) { chrome.setBreadcrumbs(getEditBreadcrumbs(savedVis.title)); chrome.docTitle.change(savedVis.title); + chrome.recentlyAccessed.add( + savedVisInstance.savedVis.getFullPath(), + savedVisInstance.savedVis.title, + savedVis.id, + { + type: savedVisInstance.savedVis.getOpenSearchType(), + } + ); } else { chrome.setBreadcrumbs(getCreateBreadcrumbs()); }