diff --git a/src/core/public/chrome/ui/header/recent_items.test.tsx b/src/core/public/chrome/ui/header/recent_items.test.tsx index d01912e9c27f..28bae880fcfa 100644 --- a/src/core/public/chrome/ui/header/recent_items.test.tsx +++ b/src/core/public/chrome/ui/header/recent_items.test.tsx @@ -18,7 +18,7 @@ jest.mock('./nav_link', () => ({ }), })); -const mockRecentlyAccessed = new BehaviorSubject([ +const mockRecentlyAccessed$ = new BehaviorSubject([ { id: '6ef856c0-5f86-11ef-b7df-1bb1cf26ce5b', label: 'visualizeMock', @@ -28,7 +28,7 @@ const mockRecentlyAccessed = new BehaviorSubject([ }, ]); -const mockWorkspaceList = new BehaviorSubject([ +const mockWorkspaceList$ = new BehaviorSubject([ { id: 'workspace_1', name: 'WorkspaceMock_1', @@ -49,7 +49,14 @@ const defaultMockProps = { navigateToUrl: applicationServiceMock.createStartContract().navigateToUrl, workspaceList$: new BehaviorSubject([]), recentlyAccessed$: new BehaviorSubject([]), - navLinks$: new BehaviorSubject([]), + navLinks$: new BehaviorSubject([ + { + id: '', + title: '', + baseUrl: '', + href: '', + }, + ]), basePath: httpServiceMock.createStartContract().basePath, http: httpServiceMock.createSetupContract(), renderBreadcrumbs: <>, @@ -85,7 +92,8 @@ describe('Recent items', () => { it('should be able to render recent works', async () => { const mockProps = { ...defaultMockProps, - recentlyAccessed$: mockRecentlyAccessed, + recentlyAccessed$: mockRecentlyAccessed$, + workspaceList$: mockWorkspaceList$, }; await act(async () => { @@ -97,11 +105,11 @@ describe('Recent items', () => { expect(screen.getByText('visualizeMock')).toBeInTheDocument(); }); - it('shoulde be able to display workspace name if the asset is attched to a workspace and render it with brackets wrapper ', async () => { + it('should be able to display workspace name if the asset is attched to a workspace and render it with brackets wrapper ', async () => { const mockProps = { ...defaultMockProps, - recentlyAccessed$: mockRecentlyAccessed, - workspaceList$: mockWorkspaceList, + recentlyAccessed$: mockRecentlyAccessed$, + workspaceList$: mockWorkspaceList$, }; await act(async () => { @@ -116,8 +124,8 @@ describe('Recent items', () => { it('should call navigateToUrl with link generated from createRecentNavLink when clicking a recent item', async () => { const mockProps = { ...defaultMockProps, - recentlyAccessed$: mockRecentlyAccessed, - workspaceList$: mockWorkspaceList, + recentlyAccessed$: mockRecentlyAccessed$, + workspaceList$: mockWorkspaceList$, }; const navigateToUrl = jest.fn(); @@ -137,7 +145,7 @@ describe('Recent items', () => { it('should be able to display the preferences popover setting when clicking Preferences button', async () => { const mockProps = { ...defaultMockProps, - recentlyAccessed$: mockRecentlyAccessed, + recentlyAccessed$: mockRecentlyAccessed$, }; await act(async () => { @@ -158,4 +166,9 @@ describe('Recent items', () => { ); expect(baseElement).toMatchSnapshot(); }); + + it('should show not display item if it is in a workspace which is not available', () => { + render(); + expect(screen.queryByText('visualizeMock')).not.toBeInTheDocument(); + }); }); diff --git a/src/core/public/chrome/ui/header/recent_items.tsx b/src/core/public/chrome/ui/header/recent_items.tsx index 7efd276b8fa9..3194be43be9d 100644 --- a/src/core/public/chrome/ui/header/recent_items.tsx +++ b/src/core/public/chrome/ui/header/recent_items.tsx @@ -2,7 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo, useState, useEffect } from 'react'; +import React, { useMemo, useState, useEffect, useRef } from 'react'; import * as Rx from 'rxjs'; import moment from 'moment'; import { @@ -122,6 +122,7 @@ export const RecentItems = ({ [] ); const navLinks = useObservable(navLinks$, []); + const prevNavLinksRef = useRef(); const loadingCount = useObservable(loadingCount$, 0); const handleItemClick = (link: string) => { @@ -143,7 +144,9 @@ export const RecentItems = ({ setIsPreferencesPopoverOpen((IsPreferencesPopoverOpe) => !IsPreferencesPopoverOpe); }} > - Preferences + {i18n.translate('core.header.recent.preferences', { + defaultMessage: 'Preferences', + })} } isOpen={isPreferencesPopoverOpen} @@ -152,7 +155,11 @@ export const RecentItems = ({ setIsPreferencesPopoverOpen(false); }} > - Preferences + + {i18n.translate('core.header.recent.preferences.title', { + defaultMessage: 'Preferences', + })} + Recents, + children: ( + + {i18n.translate('core.header.recent.preferences.legend', { + defaultMessage: 'Recents', + })} + + ), }} /> @@ -213,11 +226,14 @@ export const RecentItems = ({ type: item.meta?.type || '', id: item.id, })); - - if (savedObjects.length) { + // Deep compare navLinks to avoid unnecessary requests + if ( + savedObjects.length && + JSON.stringify(prevNavLinksRef.current) !== JSON.stringify(navLinks) + ) { bulkGetDetail(savedObjects, http).then((res) => { const filteredNavLinks = navLinks.filter((link) => !link.hidden); - const formatDetailedSavedObjects = res.map((obj) => { + const formatDetailedSavedObjects = res.flatMap((obj) => { const recentAccessItem = recentlyAccessedItems.find( (item) => item.id === obj.id ) as ChromeRecentlyAccessedHistoryItem; @@ -225,33 +241,30 @@ export const RecentItems = ({ const findWorkspace = workspaceList.find( (workspace) => workspace.id === recentAccessItem.workspaceId ); - return { - ...recentAccessItem, - ...obj, - ...recentAccessItem.meta, - updatedAt: moment(obj?.updated_at).valueOf(), - workspaceName: findWorkspace?.name, - link: createRecentNavLink(recentAccessItem, filteredNavLinks, basePath, navigateToUrl) - .href, - }; + // If the workspace id is existing but the workspace is deleted, filter the item + if (recentAccessItem.workspaceId && !findWorkspace) { + return []; + } + + return [ + { + ...recentAccessItem, + ...obj, + ...recentAccessItem.meta, + updatedAt: moment(obj?.updated_at).valueOf(), + workspaceName: findWorkspace?.name, + link: createRecentNavLink(recentAccessItem, filteredNavLinks, basePath, navigateToUrl) + .href, + }, + ]; }); - // here I write this argument to avoid Unnecessary re-rendering - if (JSON.stringify(formatDetailedSavedObjects) !== JSON.stringify(detailedSavedObjects)) { - setDetailedSavedObjects(formatDetailedSavedObjects); - } + setDetailedSavedObjects(formatDetailedSavedObjects); }); } - }, [ - navLinks, - basePath, - navigateToUrl, - recentlyAccessedItems, - http, - workspaceList, - detailedSavedObjects, - ]); + prevNavLinksRef.current = navLinks; + }, [navLinks, basePath, navigateToUrl, recentlyAccessedItems, http, workspaceList]); - const selectedRecentsItems = useMemo(() => { + const selectedRecentItems = useMemo(() => { return detailedSavedObjects.slice(0, Number(recentsRadioIdSelected)); }, [detailedSavedObjects, recentsRadioIdSelected]); @@ -283,9 +296,9 @@ export const RecentItems = ({ - {selectedRecentsItems.length > 0 ? ( + {selectedRecentItems.length > 0 ? ( - {selectedRecentsItems.map((item) => ( + {selectedRecentItems.map((item) => ( handleItemClick(item.link)} key={item.link} @@ -309,7 +322,9 @@ export const RecentItems = ({ ) : ( - No recently viewed items + {i18n.translate('core.header.recent.no.recents', { + defaultMessage: 'No recently viewed items', + })} )}