From 3cc6e18253e9225ee4a9424543f952d3f0a1973c Mon Sep 17 00:00:00 2001 From: Yuye Zhu Date: Wed, 18 Oct 2023 09:50:18 +0800 Subject: [PATCH 1/6] [Workspace][Feature] Left navigation menu adjustment (#192) * add util function to filter workspace feature by wildcard Signed-off-by: Yulong Ruan * resolve conflict Signed-off-by: yuye-aws * update tests and snapshots Signed-off-by: yuye-aws * small adjustment to left menu Signed-off-by: yuye-aws * resolve git conflict Signed-off-by: yuye-aws * rename nav link service function Signed-off-by: yuye-aws * unit test for workspace plugin.ts Signed-off-by: yuye-aws * update snapshots Signed-off-by: yuye-aws * optimize code Signed-off-by: yuye-aws * optimize code Signed-off-by: yuye-aws * optimize code Signed-off-by: yuye-aws * optimize code Signed-off-by: yuye-aws * optimize code Signed-off-by: yuye-aws * optimize code Signed-off-by: yuye-aws --------- Signed-off-by: Yulong Ruan Signed-off-by: yuye-aws Co-authored-by: Yulong Ruan --- src/core/public/chrome/chrome_service.mock.ts | 4 +- .../nav_links/nav_links_service.test.ts | 143 +- .../chrome/nav_links/nav_links_service.ts | 40 +- .../collapsible_nav.test.tsx.snap | 1400 +++++++++++++++-- .../header/__snapshots__/header.test.tsx.snap | 328 ++++ .../chrome/ui/header/collapsible_nav.test.tsx | 2 +- .../chrome/ui/header/collapsible_nav.tsx | 138 +- src/core/public/chrome/ui/header/nav_link.tsx | 4 +- src/core/public/index.ts | 2 +- .../workspace/workspaces_service.mock.ts | 7 +- .../public/workspace/workspaces_service.ts | 5 +- src/core/types/workspace.ts | 4 + .../dashboard_listing.test.tsx.snap | 20 +- .../dashboard_top_nav.test.tsx.snap | 24 +- .../objects_table/saved_objects_table.tsx | 2 +- src/plugins/workspace/public/plugin.test.ts | 29 +- src/plugins/workspace/public/plugin.ts | 115 +- 17 files changed, 1981 insertions(+), 286 deletions(-) diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index f69c13c8fc0e..566a6b7095e5 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -43,9 +43,9 @@ const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { getHeaderComponent: jest.fn(), navLinks: { + setNavLinks: jest.fn(), getNavLinks$: jest.fn(), - getFilteredNavLinks$: jest.fn(), - setFilteredNavLinks: jest.fn(), + getAllNavLinks$: jest.fn(), has: jest.fn(), get: jest.fn(), getAll: jest.fn(), diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts index 3fe2b57676e0..d4cfb2630496 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.test.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -32,18 +32,12 @@ import { NavLinksService } from './nav_links_service'; import { take, map, takeLast } from 'rxjs/operators'; import { App } from '../../application'; import { BehaviorSubject } from 'rxjs'; +import { ChromeNavLink } from 'opensearch-dashboards/public'; const availableApps = new Map([ ['app1', { id: 'app1', order: 0, title: 'App 1', icon: 'app1' }], - [ - 'app2', - { - id: 'app2', - order: -10, - title: 'App 2', - euiIconType: 'canvasApp', - }, - ], + ['app2', { id: 'app2', order: -10, title: 'App 2', euiIconType: 'canvasApp' }], + ['app3', { id: 'app3', order: 10, title: 'App 3', icon: 'app3' }], ['chromelessApp', { id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true }], ]); @@ -66,7 +60,110 @@ describe('NavLinksService', () => { start = service.start({ application: mockAppService, http: mockHttp }); }); - describe('#getNavLinks$()', () => { + describe('#getAllNavLinks$()', () => { + it('does not include `chromeless` applications', async () => { + expect( + await start + .getAllNavLinks$() + .pipe( + take(1), + map((links) => links.map((l) => l.id)) + ) + .toPromise() + ).not.toContain('chromelessApp'); + }); + + it('sorts navLinks by `order` property', async () => { + expect( + await start + .getAllNavLinks$() + .pipe( + take(1), + map((links) => links.map((l) => l.id)) + ) + .toPromise() + ).toEqual(['app2', 'app1', 'app3']); + }); + + it('emits multiple values', async () => { + const navLinkIds$ = start.getAllNavLinks$().pipe(map((links) => links.map((l) => l.id))); + const emittedLinks: string[][] = []; + navLinkIds$.subscribe((r) => emittedLinks.push(r)); + start.update('app1', { href: '/foo' }); + + service.stop(); + expect(emittedLinks).toEqual([ + ['app2', 'app1', 'app3'], + ['app2', 'app1', 'app3'], + ]); + }); + + it('completes when service is stopped', async () => { + const last$ = start.getAllNavLinks$().pipe(takeLast(1)).toPromise(); + service.stop(); + await expect(last$).resolves.toBeInstanceOf(Array); + }); + }); + + describe('#getNavLinks$() when non null', () => { + // set filtered nav links, nav link with order smaller than 0 will be filtered + beforeEach(() => { + const filteredNavLinks = new Map(); + start.getAllNavLinks$().subscribe((links) => + links.forEach((link) => { + if (link.order !== undefined && link.order >= 0) { + filteredNavLinks.set(link.id, link); + } + }) + ); + start.setNavLinks(filteredNavLinks); + }); + + it('does not include `app2` applications', async () => { + expect( + await start + .getNavLinks$() + .pipe( + take(1), + map((links) => links.map((l) => l.id)) + ) + .toPromise() + ).not.toContain('app2'); + }); + + it('sorts navLinks by `order` property', async () => { + expect( + await start + .getNavLinks$() + .pipe( + take(1), + map((links) => links.map((l) => l.id)) + ) + .toPromise() + ).toEqual(['app1', 'app3']); + }); + + it('emits multiple values', async () => { + const navLinkIds$ = start.getNavLinks$().pipe(map((links) => links.map((l) => l.id))); + const emittedLinks: string[][] = []; + navLinkIds$.subscribe((r) => emittedLinks.push(r)); + start.update('app1', { href: '/foo' }); + + service.stop(); + expect(emittedLinks).toEqual([ + ['app1', 'app3'], + ['app1', 'app3'], + ]); + }); + + it('completes when service is stopped', async () => { + const last$ = start.getNavLinks$().pipe(takeLast(1)).toPromise(); + service.stop(); + await expect(last$).resolves.toBeInstanceOf(Array); + }); + }); + + describe('#getNavLinks$() when null', () => { it('does not include `chromeless` applications', async () => { expect( await start @@ -79,7 +176,19 @@ describe('NavLinksService', () => { ).not.toContain('chromelessApp'); }); - it('sorts navlinks by `order` property', async () => { + it('include `app2` applications', async () => { + expect( + await start + .getNavLinks$() + .pipe( + take(1), + map((links) => links.map((l) => l.id)) + ) + .toPromise() + ).toContain('app2'); + }); + + it('sorts navLinks by `order` property', async () => { expect( await start .getNavLinks$() @@ -88,7 +197,7 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['app2', 'app1']); + ).toEqual(['app2', 'app1', 'app3']); }); it('emits multiple values', async () => { @@ -99,8 +208,8 @@ describe('NavLinksService', () => { service.stop(); expect(emittedLinks).toEqual([ - ['app2', 'app1'], - ['app2', 'app1'], + ['app2', 'app1', 'app3'], + ['app2', 'app1', 'app3'], ]); }); @@ -123,7 +232,7 @@ describe('NavLinksService', () => { describe('#getAll()', () => { it('returns a sorted array of navlinks', () => { - expect(start.getAll().map((l) => l.id)).toEqual(['app2', 'app1']); + expect(start.getAll().map((l) => l.id)).toEqual(['app2', 'app1', 'app3']); }); }); @@ -148,7 +257,7 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['app2', 'app1']); + ).toEqual(['app2', 'app1', 'app3']); }); it('does nothing on chromeless applications', async () => { @@ -161,7 +270,7 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['app2', 'app1']); + ).toEqual(['app2', 'app1', 'app3']); }); it('removes all other links', async () => { diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index ddbef0beed6d..89b3205cbc26 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -54,14 +54,14 @@ export interface ChromeNavLinks { getNavLinks$(): Observable>>; /** - * Set an observable for a sorted list of filtered navlinks. + * Get an observable for a sorted list of all navlinks. */ - getFilteredNavLinks$(): Observable>>; + getAllNavLinks$(): Observable>>; /** - * Set filtered navlinks. + * Set navlinks. */ - setFilteredNavLinks(filteredNavLinks: ReadonlyMap): void; + setNavLinks(navLinks: ReadonlyMap): void; /** * Get the state of a navlink at this point in time. @@ -145,7 +145,10 @@ export class NavLinksService { // manual link modifications to be able to re-apply then after every // availableApps$ changes. const linkUpdaters$ = new BehaviorSubject([]); - const navLinks$ = new BehaviorSubject>(new Map()); + const displayedNavLinks$ = new BehaviorSubject | undefined>( + undefined + ); + const allNavLinks$ = new BehaviorSubject>(new Map()); combineLatest([appLinks$, linkUpdaters$]) .pipe( @@ -154,14 +157,27 @@ export class NavLinksService { }) ) .subscribe((navLinks) => { - navLinks$.next(navLinks); + allNavLinks$.next(navLinks); }); const forceAppSwitcherNavigation$ = new BehaviorSubject(false); return { getNavLinks$: () => { - return navLinks$.pipe(map(sortNavLinks), takeUntil(this.stop$)); + return combineLatest([allNavLinks$, displayedNavLinks$]).pipe( + map(([allNavLinks, displayedNavLinks]) => + displayedNavLinks === undefined ? sortLinks(allNavLinks) : sortLinks(displayedNavLinks) + ), + takeUntil(this.stop$) + ); + }, + + setNavLinks: (navLinks: ReadonlyMap) => { + displayedNavLinks$.next(navLinks); + }, + + getAllNavLinks$: () => { + return allNavLinks$.pipe(map(sortLinks), takeUntil(this.stop$)); }, setFilteredNavLinks: (filteredNavLinks: ReadonlyMap) => { @@ -180,16 +196,16 @@ export class NavLinksService { }, get(id: string) { - const link = navLinks$.value.get(id); + const link = allNavLinks$.value.get(id); return link && link.properties; }, getAll() { - return sortNavLinks(navLinks$.value); + return sortLinks(allNavLinks$.value); }, has(id: string) { - return navLinks$.value.has(id); + return allNavLinks$.value.has(id); }, showOnly(id: string) { @@ -237,9 +253,9 @@ export class NavLinksService { } } -function sortNavLinks(navLinks: ReadonlyMap) { +function sortLinks(links: ReadonlyMap) { return sortBy( - [...navLinks.values()].map((link) => link.properties), + [...links.values()].map((link) => ('properties' in link ? link.properties : link)), 'order' ); } diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index b732d2904e22..f8a85ea8991d 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -85,7 +85,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` } getUrlForApp={[MockFunction]} homeHref="/" - id="collapsibe-nav" + id="collapsible-nav" isLocked={false} isNavOpen={true} logos={ @@ -382,7 +382,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` + +
+ +
+
+ + + +
+
+
+
+
+ +
+
@@ -465,11 +575,268 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` - - - + + + + + + + + + + + + + +

+ Recently Visited +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="clock" + data-test-subj="collapsibleNavGroup-recentlyVisited" + id="mockId" + initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} + onToggle={[Function]} + paddingSize="none" + > +
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
- +
- -
-
- -
- -
- - - -
-
- -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
-
-
-
-
+ +
+ + + OpenSearch Dashboards + + +
+
+
+
+ + + + +
+ + + + + + + +

+ Recently Visited +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="clock" + data-test-subj="collapsibleNavGroup-recentlyVisited" + id="mockId" + initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} + onToggle={[Function]} + paddingSize="none" + > +
+
+ +
+
+ +
+
- -
+ - - - OpenSearch Dashboards - - -
-
+ + +
- -
- +
+ + - +
- - - OpenSearch Dashboards - - - - + + + OpenSearch Dashboards + + + + + + + + + + + + + + + + + + +

+ Recently Visited +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="clock" + data-test-subj="collapsibleNavGroup-recentlyVisited" + id="mockId" + initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} + onToggle={[Function]} + paddingSize="none" + > +
+
+ +
+
+ +
+
+
+ + + +
- -
- +
+ +
- +
- - - + + + + + + + + + + + + + +

+ Recently Visited +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="clock" + data-test-subj="collapsibleNavGroup-recentlyVisited" + id="mockId" + initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} + onToggle={[Function]} + paddingSize="none" + > +
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
- +
+ + + + + + + +

+ Recently Visited +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="clock" + data-test-subj="collapsibleNavGroup-recentlyVisited" + id="mockId" + initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} + onToggle={[Function]} + paddingSize="none" + > +
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+ +
+
+ +
    + +
  • + +
  • +
    +
+
+
+
+
+
+
+ +
+
@@ -6427,6 +6537,224 @@ exports[`Header handles visibility and lock changes 1`] = `
+ + + + + + + +

+ Recently Visited +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="clock" + data-test-subj="collapsibleNavGroup-recentlyVisited" + id="mockId" + initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} + onToggle={[Function]} + paddingSize="none" + > +
+
+ +
+
+ + + +
+
+
+
, - categoryDictionary: ReturnType -) { - return sortBy( - Object.keys(mainCategories), - (categoryName) => categoryDictionary[categoryName]?.order - ); -} - -function getMergedNavLinks( - orderedCategories: string[], +function getSortedLinksAndCategories( uncategorizedLinks: CollapsibleNavLink[], categoryDictionary: ReturnType -): Array { - const uncategorizedLinksWithOrder = sortBy( - uncategorizedLinks.filter((link) => link.order !== null), - 'order' - ); +): Array { + // uncategorized links and categories are ranked according the order + // if order is not defined, categories will be placed above uncategorized links + const categories = Object.values(categoryDictionary).filter( + (category) => category !== undefined + ) as AppCategory[]; + const uncategorizedLinksWithOrder = uncategorizedLinks.filter((link) => link.order !== null); const uncategorizedLinksWithoutOrder = uncategorizedLinks.filter((link) => link.order === null); - const orderedCategoryWithOrder = orderedCategories - .filter((categoryName) => categoryDictionary[categoryName]?.order !== null) - .map((categoryName) => ({ categoryName, order: categoryDictionary[categoryName]?.order })); - const orderedCategoryWithoutOrder = orderedCategories.filter( - (categoryName) => categoryDictionary[categoryName]?.order === null - ); - const mergedNavLinks = sortBy( - [...uncategorizedLinksWithOrder, ...orderedCategoryWithOrder], + const categoriesWithOrder = categories.filter((category) => category.order !== null); + const categoriesWithoutOrder = categories.filter((category) => category.order === null); + const sortedLinksAndCategories = sortBy( + [...uncategorizedLinksWithOrder, ...categoriesWithOrder], 'order' - ).map((navLink) => ('categoryName' in navLink ? navLink.categoryName : navLink)); - // if order is not defined , categorized links will be placed before uncategorized links - return [...mergedNavLinks, ...orderedCategoryWithoutOrder, ...uncategorizedLinksWithoutOrder]; + ); + return [ + ...sortedLinksAndCategories, + ...categoriesWithoutOrder, + ...uncategorizedLinksWithoutOrder, + ]; } function getCategoryLocalStorageKey(id: string) { @@ -153,6 +145,10 @@ export function CollapsibleNav({ ...observables }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + let customNavLink = useObservable(observables.customNavLink$, undefined); + if (customNavLink) { + customNavLink = { ...customNavLink, externalLink: true }; + } const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); const allNavLinks: CollapsibleNavLink[] = [...navLinks]; if (recentlyAccessed.length) { @@ -167,9 +163,7 @@ export function CollapsibleNav({ const groupedNavLinks = groupBy(allNavLinks, (link) => link?.category?.id); const { undefined: uncategorizedLinks = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); - const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); - const mergedNavLinks = getMergedNavLinks( - orderedCategories, + const sortedLinksAndCategories = getSortedLinksAndCategories( uncategorizedLinks, categoryDictionary ); @@ -204,30 +198,62 @@ export function CollapsibleNav({ onClose={closeNav} outsideClickCloses={false} > - - {collapsibleNavHeaderRender ? ( - collapsibleNavHeaderRender() - ) : ( - - - - - - - - {defaultHeaderName} - - - - - )} + {collapsibleNavHeaderRender ? ( + collapsibleNavHeaderRender() + ) : ( + + + + + + + + {defaultHeaderName} + + + + + )} - {/* merged NavLinks */} - {mergedNavLinks.map((item, i) => { - if (typeof item === 'string') { - const category = categoryDictionary[item]!; + {customNavLink && ( + + + + + + + + + + )} + + + {sortedLinksAndCategories.map((item, i) => { + if (!('href' in item)) { + // CollapsibleNavLink has href property, while AppCategory does not have + const category = item; const opensearchLinkLogo = - category.id === 'opensearchDashboards' ? logos.Mark.url : category.euiIconType; + category.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id + ? logos.Mark.url + : category.euiIconType; return ( readyForEUI(link))} + listItems={allCategorizedLinks[item.id].map((link) => readyForEUI(link))} maxWidth="none" color="subdued" gutterSize="none" diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index e8b335db1015..e0a71f38c38f 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -40,7 +40,7 @@ export const isModifiedOrPrevented = (event: React.MouseEvent(''); -const workspaceList$ = new BehaviorSubject([]); -const currentWorkspace$ = new BehaviorSubject(null); +const workspaceList$ = new BehaviorSubject([]); +const currentWorkspace$ = new BehaviorSubject(null); const initialized$ = new BehaviorSubject(false); const createWorkspacesSetupContractMock = () => ({ diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index 09d394f098ea..386bb1c6e393 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -5,10 +5,7 @@ import { BehaviorSubject, combineLatest } from 'rxjs'; import { isEqual } from 'lodash'; - -import { CoreService, WorkspaceAttribute } from '../../types'; - -type WorkspaceObject = WorkspaceAttribute & { libraryReadonly?: boolean }; +import { CoreService, WorkspaceObject } from '../../types'; interface WorkspaceObservables { /** diff --git a/src/core/types/workspace.ts b/src/core/types/workspace.ts index e99744183cac..d66a93fcc61d 100644 --- a/src/core/types/workspace.ts +++ b/src/core/types/workspace.ts @@ -13,3 +13,7 @@ export interface WorkspaceAttribute { defaultVISTheme?: string; reserved?: boolean; } + +export interface WorkspaceObject extends WorkspaceAttribute { + readonly?: boolean; +} diff --git a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap index b452b5c867ff..9679c0cfdccf 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -223,11 +223,11 @@ exports[`dashboard listing hideWriteControls 1`] = ` "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -1359,11 +1359,11 @@ exports[`dashboard listing render table listing with initial filters from URL 1` "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -2556,11 +2556,11 @@ exports[`dashboard listing renders call to action when no dashboards exist 1`] = "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -3753,11 +3753,11 @@ exports[`dashboard listing renders table rows 1`] = ` "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -4950,11 +4950,11 @@ exports[`dashboard listing renders warning when listingLimit is exceeded 1`] = ` "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap index 9b8b911685a9..5e05e64ba8cd 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap @@ -211,11 +211,11 @@ exports[`Dashboard top nav render in embed mode 1`] = ` "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -1172,11 +1172,11 @@ exports[`Dashboard top nav render in embed mode, and force hide filter bar 1`] = "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -2133,11 +2133,11 @@ exports[`Dashboard top nav render in embed mode, components can be forced show b "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -3094,11 +3094,11 @@ exports[`Dashboard top nav render in full screen mode with appended URL param bu "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -4055,11 +4055,11 @@ exports[`Dashboard top nav render in full screen mode, no componenets should be "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, @@ -5016,11 +5016,11 @@ exports[`Dashboard top nav render with all components 1`] = ` "enableForcedAppSwitcherNavigation": [MockFunction], "get": [MockFunction], "getAll": [MockFunction], - "getFilteredNavLinks$": [MockFunction], + "getAllNavLinks$": [MockFunction], "getForceAppSwitcherNavigation$": [MockFunction], "getNavLinks$": [MockFunction], "has": [MockFunction], - "setFilteredNavLinks": [MockFunction], + "setNavLinks": [MockFunction], "showOnly": [MockFunction], "update": [MockFunction], }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 059c623dfb68..9f1778236a99 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -1111,7 +1111,7 @@ export class SavedObjectsTable extends Component this.setState({ diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts index 3b0b54569cbd..9a476b60d208 100644 --- a/src/plugins/workspace/public/plugin.test.ts +++ b/src/plugins/workspace/public/plugin.test.ts @@ -3,8 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Observable, Subscriber } from 'rxjs'; +import { Observable, Subscriber, of } from 'rxjs'; import { waitFor } from '@testing-library/dom'; +import { ChromeNavLink } from 'opensearch-dashboards/public'; import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.mock'; import { applicationServiceMock, chromeServiceMock, coreMock } from '../../../core/public/mocks'; import { WorkspacePlugin } from './plugin'; @@ -131,4 +132,30 @@ describe('Workspace plugin', () => { expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_OVERVIEW_APP_ID); windowSpy.mockRestore(); }); + + it('#start filter nav links according to workspace feature', () => { + const workspacePlugin = new WorkspacePlugin(); + const coreStart = coreMock.createStart(); + const navLinksService = coreStart.chrome.navLinks; + const devToolsNavLink = { + id: 'dev_tools', + category: { id: 'management', label: 'Management' }, + }; + const discoverNavLink = { + id: 'discover', + category: { id: 'opensearchDashboards', label: 'Library' }, + }; + const workspace = { + id: 'test', + name: 'test', + features: ['dev_tools'], + }; + const allNavLinks = of([devToolsNavLink, discoverNavLink] as ChromeNavLink[]); + const filteredNavLinksMap = new Map(); + filteredNavLinksMap.set(devToolsNavLink.id, devToolsNavLink as ChromeNavLink); + navLinksService.getAllNavLinks$.mockReturnValue(allNavLinks); + coreStart.workspaces.currentWorkspace$.next(workspace); + workspacePlugin.start(coreStart); + expect(navLinksService.setNavLinks).toHaveBeenCalledWith(filteredNavLinksMap); + }); }); diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 5592a32390fd..f356fcd33645 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -14,7 +14,7 @@ import { CoreSetup, CoreStart, Plugin, - WorkspaceAttribute, + WorkspaceObject, DEFAULT_APP_CATEGORIES, } from '../../../core/public'; import { @@ -42,12 +42,66 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> private getWorkspaceIdFromURL(): string | null { return getWorkspaceIdFromUrl(window.location.href); } + + private filterByWorkspace(workspace: WorkspaceObject | null, allNavLinks: ChromeNavLink[]) { + if (!workspace) return allNavLinks; + const features = workspace.features ?? ['*']; + return allNavLinks.filter(featureMatchesConfig(features)); + } + + private filterNavLinks(core: CoreStart) { + const navLinksService = core.chrome.navLinks; + const allNavLinks$ = navLinksService.getAllNavLinks$(); + const currentWorkspace$ = core.workspaces.currentWorkspace$; + combineLatest([ + allNavLinks$.pipe(map(this.changeCategoryNameByWorkspaceFeatureFlag)), + currentWorkspace$, + ]).subscribe(([allNavLinks, currentWorkspace]) => { + const filteredNavLinks = this.filterByWorkspace(currentWorkspace, allNavLinks); + const navLinks = new Map(); + filteredNavLinks.forEach((chromeNavLink) => { + navLinks.set(chromeNavLink.id, chromeNavLink); + }); + navLinksService.setNavLinks(navLinks); + }); + } + + /** + * The category "Opensearch Dashboards" needs to be renamed as "Library" + * when workspace feature flag is on, we need to do it here and generate + * a new item without polluting the original ChromeNavLink. + */ + private changeCategoryNameByWorkspaceFeatureFlag(chromeLinks: ChromeNavLink[]): ChromeNavLink[] { + return chromeLinks.map((item) => { + if (item.category?.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id) { + return { + ...item, + category: { + ...item.category, + label: i18n.translate('core.ui.libraryNavList.label', { + defaultMessage: 'Library', + }), + }, + }; + } + return item; + }); + } + + private _changeSavedObjectCurrentWorkspace() { + if (this.coreStart) { + return this.coreStart.workspaces.currentWorkspaceId$.subscribe((currentWorkspaceId) => { + if (currentWorkspaceId) { + this.coreStart?.savedObjects.client.setCurrentWorkspace(currentWorkspaceId); + } + }); + } + } + public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacePluginSetupDeps) { core.chrome.registerCollapsibleNavHeader(renderWorkspaceMenu); - const workspaceClient = new WorkspaceClient(core.http, core.workspaces); await workspaceClient.init(); - /** * Retrieve workspace id from url */ @@ -169,61 +223,6 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> return {}; } - private _changeSavedObjectCurrentWorkspace() { - if (this.coreStart) { - return this.coreStart.workspaces.currentWorkspaceId$.subscribe((currentWorkspaceId) => { - if (currentWorkspaceId) { - this.coreStart?.savedObjects.client.setCurrentWorkspace(currentWorkspaceId); - } - }); - } - } - - private filterByWorkspace(workspace: WorkspaceAttribute | null, allNavLinks: ChromeNavLink[]) { - if (!workspace) return allNavLinks; - const features = workspace.features ?? ['*']; - return allNavLinks.filter(featureMatchesConfig(features)); - } - - private filterNavLinks(core: CoreStart) { - const navLinksService = core.chrome.navLinks; - const chromeNavLinks$ = navLinksService.getNavLinks$(); - const currentWorkspace$ = core.workspaces.currentWorkspace$; - combineLatest([ - chromeNavLinks$.pipe(map(this.changeCategoryNameByWorkspaceFeatureFlag)), - currentWorkspace$, - ]).subscribe(([chromeNavLinks, currentWorkspace]) => { - const filteredNavLinks = new Map(); - chromeNavLinks = this.filterByWorkspace(currentWorkspace, chromeNavLinks); - chromeNavLinks.forEach((chromeNavLink) => { - filteredNavLinks.set(chromeNavLink.id, chromeNavLink); - }); - navLinksService.setFilteredNavLinks(filteredNavLinks); - }); - } - - /** - * The category "Opensearch Dashboards" needs to be renamed as "Library" - * when workspace feature flag is on, we need to do it here and generate - * a new item without polluting the original ChromeNavLink. - */ - private changeCategoryNameByWorkspaceFeatureFlag(chromeLinks: ChromeNavLink[]): ChromeNavLink[] { - return chromeLinks.map((item) => { - if (item.category?.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id) { - return { - ...item, - category: { - ...item.category, - label: i18n.translate('core.ui.libraryNavList.label', { - defaultMessage: 'Library', - }), - }, - }; - } - return item; - }); - } - public start(core: CoreStart) { this.coreStart = core; From 39df66e154e170383854e1970535ff7a91d5fc28 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Wed, 18 Oct 2023 11:08:06 +0800 Subject: [PATCH 2/6] resolve conflict Signed-off-by: yuye-aws --- src/core/public/chrome/chrome_service.tsx | 2 +- .../chrome/nav_links/nav_links_service.ts | 25 ------------------- src/core/public/chrome/ui/header/nav_link.tsx | 2 +- .../public/workspace/workspaces_service.ts | 4 +-- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index cdbc58723dbf..928bfebf78f3 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -277,7 +277,7 @@ export class ChromeService { homeHref={http.basePath.prepend('/app/home')} isVisible$={this.isVisible$} opensearchDashboardsVersion={injectedMetadata.getOpenSearchDashboardsVersion()} - navLinks$={navLinks.getFilteredNavLinks$()} + navLinks$={navLinks.getNavLinks$()} customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))} recentlyAccessed$={recentlyAccessed.get$()} navControlsLeft$={navControls.getLeft$()} diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index 89b3205cbc26..d4c899a57be8 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -126,9 +126,6 @@ type LinksUpdater = (navLinks: Map) => Map | undefined>( - undefined - ); public start({ application, http }: StartDeps): ChromeNavLinks { const appLinks$ = application.applications$.pipe( @@ -180,21 +177,6 @@ export class NavLinksService { return allNavLinks$.pipe(map(sortLinks), takeUntil(this.stop$)); }, - setFilteredNavLinks: (filteredNavLinks: ReadonlyMap) => { - this.filteredNavLinks$.next(filteredNavLinks); - }, - - getFilteredNavLinks$: () => { - return combineLatest([navLinks$, this.filteredNavLinks$]).pipe( - map(([navLinks, filteredNavLinks]) => - filteredNavLinks === undefined - ? sortNavLinks(navLinks) - : sortChromeNavLinks(filteredNavLinks) - ), - takeUntil(this.stop$) - ); - }, - get(id: string) { const link = allNavLinks$.value.get(id); return link && link.properties; @@ -259,10 +241,3 @@ function sortLinks(links: ReadonlyMap) { 'order' ); } - -function sortChromeNavLinks(chromeNavLinks: ReadonlyMap) { - return sortBy( - [...chromeNavLinks.values()].map((link) => link as Readonly), - 'order' - ); -} diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index e0a71f38c38f..5665e0ecb25f 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -72,7 +72,7 @@ export function createEuiListItem({ } if ( - !link.externalLink && // ignore external links + !externalLink && // ignore external links event.button === 0 && // ignore everything but left clicks !isModifiedOrPrevented(event) ) { diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index 386bb1c6e393..d9d6664582b7 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -44,8 +44,8 @@ export type WorkspacesStart = WorkspaceObservables; export class WorkspacesService implements CoreService { private currentWorkspaceId$ = new BehaviorSubject(''); - private workspaceList$ = new BehaviorSubject([]); - private currentWorkspace$ = new BehaviorSubject(null); + private workspaceList$ = new BehaviorSubject([]); + private currentWorkspace$ = new BehaviorSubject(null); private initialized$ = new BehaviorSubject(false); constructor() { From d4b5ddc2876526d939e205ce167d47237a666a85 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Wed, 18 Oct 2023 11:52:54 +0800 Subject: [PATCH 3/6] update snapshots Signed-off-by: yuye-aws --- .../collapsible_nav.test.tsx.snap | 2558 ++++++----------- .../header/__snapshots__/header.test.tsx.snap | 476 ++- .../tag_cloud_visualization.test.js.snap | 4 + 3 files changed, 1100 insertions(+), 1938 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index f8a85ea8991d..3cc044260d1c 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -79,7 +79,45 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "closed": false, "hasError": false, "isStopped": false, - "observers": Array [], + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], "thrownError": null, } } @@ -416,6 +454,55 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onClick={[Function]} type="button" /> + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + + OpenSearch Dashboards + + +
+
+
+
+
+
+
+
+
- -
-
- -
- -
- - - -
-
- -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
-
-
-
-
@@ -873,15 +911,15 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__title" id="mockId__title" > - Recently Visited + OpenSearch Dashboards } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="clock" - data-test-subj="collapsibleNavGroup-recentlyVisited" + data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_light.svg" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} @@ -891,8 +929,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` >
@@ -960,7 +998,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Recently Visited + OpenSearch Dashboards
@@ -988,25 +1026,33 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__children" >
    - recent 1 + discover @@ -1057,10 +1103,10 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` - recent 2 + visualize + + + + + +
  • + + + dashboard
  • @@ -1096,14 +1173,14 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` @@ -1130,15 +1207,15 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__title" id="mockId__title" > - OpenSearch Dashboards + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_light.svg" - data-test-subj="collapsibleNavGroup-opensearchDashboards" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} @@ -1148,8 +1225,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` >
    @@ -1217,7 +1294,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - OpenSearch Dashboards + Observability
    @@ -1245,33 +1322,25 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__children" >
      - discover + metrics @@ -1322,10 +1391,10 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` - visualize - - - - - -
    • - - - dashboard + logs
    • @@ -1392,14 +1430,14 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` @@ -1426,15 +1464,15 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__title" id="mockId__title" > - Observability + Security } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="logoObservability" - data-test-subj="collapsibleNavGroup-observability" + data-test-opensearch-logo="logoSecurity" + data-test-subj="collapsibleNavGroup-securitySolution" id="mockId" initialIsOpen={true} isLoading={false} @@ -1444,8 +1482,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` >
      @@ -1513,7 +1551,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Observability + Security
      @@ -1541,25 +1579,17 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__children" >
        -
      • - - - metrics - - -
      • - - - logs + siem @@ -1649,14 +1648,14 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` @@ -1683,15 +1682,15 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__title" id="mockId__title" > - Security + Management } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="logoSecurity" - data-test-subj="collapsibleNavGroup-securitySolution" + data-test-opensearch-logo="managementApp" + data-test-subj="collapsibleNavGroup-management" id="mockId" initialIsOpen={true} isLoading={false} @@ -1701,8 +1700,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` >
        @@ -1770,7 +1769,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Security + Management
        @@ -1798,17 +1797,17 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__children" >
          - siem + monitoring @@ -1867,252 +1866,34 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` - - - - - - -

          - Management -

          -
          -
          - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="managementApp" - data-test-subj="collapsibleNavGroup-management" +
          -
          - -
          -
          - -
          -
          -
          - - - -
          -
          -
          -
          -
          -
          - - - -
          -
          - -
            - -
          • + +
            +
            + +
            + +
            + + + +
            +
            + +
            + +
            + + + OpenSearch Dashboards + + +
            +
            +
            +
            +
            +
            +
            +
            +
            @@ -3462,7 +3406,45 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` "closed": false, "hasError": false, "isStopped": false, - "observers": Array [], + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], "thrownError": null, } } @@ -3717,61 +3699,61 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` onClick={[Function]} type="button" /> - +
            - -
            +
            +
            - +
            - -
            - - - -
            -
            - + + + +
            +
            + +
            +
            - -
            - - - OpenSearch Dashboards - - -
            -
            + + + OpenSearch Dashboards + +
            - +
            -
            +
            -
            - + +
            +
            +
            + +
            @@ -4025,15 +4007,15 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` className="euiCollapsibleNavGroup__title" id="mockId__title" > - Recently Visited + OpenSearch Dashboards } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="clock" - data-test-subj="collapsibleNavGroup-recentlyVisited" + data-test-opensearch-logo="/custom/branded/mark-darkmode.svg" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} @@ -4043,8 +4025,8 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` >
            @@ -4112,7 +4094,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Recently Visited + OpenSearch Dashboards
            @@ -4140,17 +4122,17 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` className="euiCollapsibleNavGroup__children" >
              - recent + discover @@ -4209,14 +4191,14 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` @@ -4243,15 +4225,15 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` className="euiCollapsibleNavGroup__title" id="mockId__title" > - OpenSearch Dashboards + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/custom/branded/mark-darkmode.svg" - data-test-subj="collapsibleNavGroup-opensearchDashboards" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} @@ -4261,8 +4243,8 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` >
              @@ -4330,7 +4312,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - OpenSearch Dashboards + Observability
              @@ -4358,7 +4340,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` className="euiCollapsibleNavGroup__children" >
                - - - - - - - -

                - Observability -

                -
                -
                - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="logoObservability" - data-test-subj="collapsibleNavGroup-observability" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > -
                -
                - -
                -
                - -
                -
                -
                - - - -
                -
                -
                -
                -
                -
                -
                -
                -
                - +
                - -
                +
                +
                - +
                - -
                - - - -
                -
                - -
                - -
                - - - OpenSearch Dashboards - - -
                -
                -
                -
                -
                -
                -
                -
                - - - - - - - -

                - Recently Visited -

                -
                -
                - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="clock" - data-test-subj="collapsibleNavGroup-recentlyVisited" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > -
                -
                - -
                -
                - + +
                + + +
                -
                +
                -
                - - - -
                + + + OpenSearch Dashboards + +
                -
                - -
                + +
                +
                - -
                + +
                +
                +
                + +
                - +
                - -
                +
                +
                - +
                - -
                - - - -
                -
                - + + + +
                +
                + +
                +
                - -
                - - - OpenSearch Dashboards - - -
                -
                + + + OpenSearch Dashboards + +
                - +
                -
                +
                -
                - + +
                +
                +
                + +
                @@ -6742,15 +6364,15 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode className="euiCollapsibleNavGroup__title" id="mockId__title" > - Recently Visited + OpenSearch Dashboards } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="clock" - data-test-subj="collapsibleNavGroup-recentlyVisited" + data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_dark.svg" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} @@ -6760,8 +6382,8 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode >
                @@ -6829,7 +6451,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Recently Visited + OpenSearch Dashboards
                @@ -6857,17 +6479,17 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode className="euiCollapsibleNavGroup__children" >
                  - recent + discover @@ -6926,14 +6548,14 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode @@ -6960,15 +6582,15 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode className="euiCollapsibleNavGroup__title" id="mockId__title" > - OpenSearch Dashboards + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_dark.svg" - data-test-subj="collapsibleNavGroup-opensearchDashboards" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} @@ -6978,8 +6600,8 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode >
                  @@ -7047,7 +6669,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - OpenSearch Dashboards + Observability
                  @@ -7075,7 +6697,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode className="euiCollapsibleNavGroup__children" >
                    - - - - - - - -

                    - Observability -

                    -
                    -
                    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="logoObservability" - data-test-subj="collapsibleNavGroup-observability" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > +
                    - -
                    -
                    - -
                    -
                    -
                    - - - -
                    -
                    -
                    -
                    -
                    -
                    -
                    -
                    - - -
                    -
                    - -
                      - +
                      - -
                      +
                      +
                      - +
                      - -
                      - - - -
                      -
                      - -
                      - -
                      - - - OpenSearch Dashboards - - -
                      -
                      -
                      -
                      -
                      -
                      -
                      -
                      - - - - - - - -

                      - Recently Visited -

                      -
                      -
                      - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="clock" - data-test-subj="collapsibleNavGroup-recentlyVisited" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > -
                      -
                      - -
                      -
                      - + +
                      + + +
                      -
                      +
                      -
                      - - - -
                      + + + OpenSearch Dashboards + +
                      -
                      - -
                      + +
                      +
                      - -
                      + +
                      +
                    + + +
                    + +
                    +
                    + +
                    + +
                    + + + +
                    +
                    + +
                    + +
                    + + + OpenSearch Dashboards + + +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    - -
                    -
                    - -
                    - -
                    - - - -
                    -
                    - -
                    - -
                    - - - OpenSearch Dashboards - - -
                    -
                    -
                    -
                    -
                    -
                    -
                    -
                    -
                    - - - - - - - -

                    - Recently Visited -

                    -
                    -
                    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="clock" - data-test-subj="collapsibleNavGroup-recentlyVisited" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > -
                    -
                    - -
                    -
                    - - - -
                    -
                    -
                    -
                    CNINUSDEBR"`; +exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 2`] = `"CNINUSDEBR"`; + +exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 3`] = `"CNINUSDEBR"`; + exports[`TagCloudVisualizationTest TagCloudVisualization - basics with resize 1`] = `"CNINUSDEBR"`; From 7bb84a69e45e6a759669977948e0b4297cc10b6e Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Wed, 18 Oct 2023 11:53:10 +0800 Subject: [PATCH 4/6] resolve conflicts Signed-off-by: yuye-aws --- .../objects_table/saved_objects_table.tsx | 2 +- src/plugins/workspace/public/plugin.ts | 111 +++++++++--------- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 9f1778236a99..059c623dfb68 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -1111,7 +1111,7 @@ export class SavedObjectsTable extends Component this.setState({ diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index f356fcd33645..6a5da6a84efb 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -39,65 +39,11 @@ interface WorkspacePluginSetupDeps { export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> { private coreStart?: CoreStart; private currentWorkspaceSubscription?: Subscription; + private getWorkspaceIdFromURL(): string | null { return getWorkspaceIdFromUrl(window.location.href); } - private filterByWorkspace(workspace: WorkspaceObject | null, allNavLinks: ChromeNavLink[]) { - if (!workspace) return allNavLinks; - const features = workspace.features ?? ['*']; - return allNavLinks.filter(featureMatchesConfig(features)); - } - - private filterNavLinks(core: CoreStart) { - const navLinksService = core.chrome.navLinks; - const allNavLinks$ = navLinksService.getAllNavLinks$(); - const currentWorkspace$ = core.workspaces.currentWorkspace$; - combineLatest([ - allNavLinks$.pipe(map(this.changeCategoryNameByWorkspaceFeatureFlag)), - currentWorkspace$, - ]).subscribe(([allNavLinks, currentWorkspace]) => { - const filteredNavLinks = this.filterByWorkspace(currentWorkspace, allNavLinks); - const navLinks = new Map(); - filteredNavLinks.forEach((chromeNavLink) => { - navLinks.set(chromeNavLink.id, chromeNavLink); - }); - navLinksService.setNavLinks(navLinks); - }); - } - - /** - * The category "Opensearch Dashboards" needs to be renamed as "Library" - * when workspace feature flag is on, we need to do it here and generate - * a new item without polluting the original ChromeNavLink. - */ - private changeCategoryNameByWorkspaceFeatureFlag(chromeLinks: ChromeNavLink[]): ChromeNavLink[] { - return chromeLinks.map((item) => { - if (item.category?.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id) { - return { - ...item, - category: { - ...item.category, - label: i18n.translate('core.ui.libraryNavList.label', { - defaultMessage: 'Library', - }), - }, - }; - } - return item; - }); - } - - private _changeSavedObjectCurrentWorkspace() { - if (this.coreStart) { - return this.coreStart.workspaces.currentWorkspaceId$.subscribe((currentWorkspaceId) => { - if (currentWorkspaceId) { - this.coreStart?.savedObjects.client.setCurrentWorkspace(currentWorkspaceId); - } - }); - } - } - public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacePluginSetupDeps) { core.chrome.registerCollapsibleNavHeader(renderWorkspaceMenu); const workspaceClient = new WorkspaceClient(core.http, core.workspaces); @@ -223,6 +169,61 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> return {}; } + private _changeSavedObjectCurrentWorkspace() { + if (this.coreStart) { + return this.coreStart.workspaces.currentWorkspaceId$.subscribe((currentWorkspaceId) => { + if (currentWorkspaceId) { + this.coreStart?.savedObjects.client.setCurrentWorkspace(currentWorkspaceId); + } + }); + } + } + + private filterByWorkspace(workspace: WorkspaceObject | null, allNavLinks: ChromeNavLink[]) { + if (!workspace) return allNavLinks; + const features = workspace.features ?? ['*']; + return allNavLinks.filter(featureMatchesConfig(features)); + } + + private filterNavLinks(core: CoreStart) { + const navLinksService = core.chrome.navLinks; + const allNavLinks$ = navLinksService.getAllNavLinks$(); + const currentWorkspace$ = core.workspaces.currentWorkspace$; + combineLatest([ + allNavLinks$.pipe(map(this.changeCategoryNameByWorkspaceFeatureFlag)), + currentWorkspace$, + ]).subscribe(([allNavLinks, currentWorkspace]) => { + const filteredNavLinks = this.filterByWorkspace(currentWorkspace, allNavLinks); + const navLinks = new Map(); + filteredNavLinks.forEach((chromeNavLink) => { + navLinks.set(chromeNavLink.id, chromeNavLink); + }); + navLinksService.setNavLinks(navLinks); + }); + } + + /** + * The category "Opensearch Dashboards" needs to be renamed as "Library" + * when workspace feature flag is on, we need to do it here and generate + * a new item without polluting the original ChromeNavLink. + */ + private changeCategoryNameByWorkspaceFeatureFlag(chromeLinks: ChromeNavLink[]): ChromeNavLink[] { + return chromeLinks.map((item) => { + if (item.category?.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id) { + return { + ...item, + category: { + ...item.category, + label: i18n.translate('core.ui.libraryNavList.label', { + defaultMessage: 'Library', + }), + }, + }; + } + return item; + }); + } + public start(core: CoreStart) { this.coreStart = core; From d3786df9aba27ae5e9326826dda091d637cfc007 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Wed, 18 Oct 2023 11:57:51 +0800 Subject: [PATCH 5/6] restore snapshot Signed-off-by: yuye-aws --- .../__snapshots__/tag_cloud_visualization.test.js.snap | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap index a5235707f15b..4f4e2eeab4be 100644 --- a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap +++ b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap @@ -4,8 +4,4 @@ exports[`TagCloudVisualizationTest TagCloudVisualization - basics simple draw 1` exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 1`] = `"CNINUSDEBR"`; -exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 2`] = `"CNINUSDEBR"`; - -exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 3`] = `"CNINUSDEBR"`; - exports[`TagCloudVisualizationTest TagCloudVisualization - basics with resize 1`] = `"CNINUSDEBR"`; From 942484f76b20b0f5b5f6847ec80c165d246edea8 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Wed, 18 Oct 2023 12:14:05 +0800 Subject: [PATCH 6/6] update tests Signed-off-by: yuye-aws --- .../public/chrome/nav_links/nav_links_service.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts index d4cfb2630496..8f4f5dbfa4d6 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.test.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -106,17 +106,17 @@ describe('NavLinksService', () => { }); describe('#getNavLinks$() when non null', () => { - // set filtered nav links, nav link with order smaller than 0 will be filtered + // set nav links, nav link with order smaller than 0 will be filtered beforeEach(() => { - const filteredNavLinks = new Map(); + const navLinks = new Map(); start.getAllNavLinks$().subscribe((links) => links.forEach((link) => { if (link.order !== undefined && link.order >= 0) { - filteredNavLinks.set(link.id, link); + navLinks.set(link.id, link); } }) ); - start.setNavLinks(filteredNavLinks); + start.setNavLinks(navLinks); }); it('does not include `app2` applications', async () => {