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 00e1831d9f24..1991291a1c8c 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 @@ -398,7 +398,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -568,7 +568,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -3497,7 +3497,7 @@ exports[`CollapsibleNav renders the default nav 1`] = ` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -3815,7 +3815,7 @@ exports[`CollapsibleNav renders the default nav 2`] = ` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -4133,7 +4133,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -4261,7 +4261,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -5017,7 +5017,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -5298,7 +5298,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -7781,7 +7781,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -8062,7 +8062,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -9389,7 +9389,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in default m "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -9633,7 +9633,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in default m "observers": Array [], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 7915857b3c7a..fe958c7c4e0d 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -1947,7 +1947,7 @@ exports[`Header handles visibility and lock changes 1`] = ` ], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -6339,7 +6339,7 @@ exports[`Header handles visibility and lock changes 1`] = ` ], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -6542,7 +6542,7 @@ exports[`Header handles visibility and lock changes 1`] = ` ], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -6684,9 +6684,9 @@ exports[`Header handles visibility and lock changes 1`] = ` className="euiText euiText--medium" > - + OpenSearch Dashboards - + @@ -8883,7 +8883,7 @@ exports[`Header renders condensed header 1`] = ` ], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, @@ -12243,7 +12243,7 @@ exports[`Header renders condensed header 1`] = ` ], "thrownError": null, }, - "hasFetchedWorkspaceList$": BehaviorSubject { + "initialized$": BehaviorSubject { "_isScalar": false, "_value": false, "closed": false, diff --git a/src/core/public/workspace/workspaces_service.mock.ts b/src/core/public/workspace/workspaces_service.mock.ts index ee813fffa8f4..3c35315aa850 100644 --- a/src/core/public/workspace/workspaces_service.mock.ts +++ b/src/core/public/workspace/workspaces_service.mock.ts @@ -9,14 +9,14 @@ import { WorkspaceAttribute } from '..'; const currentWorkspaceId$ = new BehaviorSubject(''); const workspaceList$ = new BehaviorSubject([]); const currentWorkspace$ = new BehaviorSubject(null); -const hasFetchedWorkspaceList$ = new BehaviorSubject(false); +const initialized$ = new BehaviorSubject(false); const workspaceEnabled$ = new BehaviorSubject(false); const createWorkspacesSetupContractMock = () => ({ currentWorkspaceId$, workspaceList$, currentWorkspace$, - hasFetchedWorkspaceList$, + initialized$, workspaceEnabled$, registerWorkspaceMenuRender: jest.fn(), }); @@ -25,7 +25,7 @@ const createWorkspacesStartContractMock = () => ({ currentWorkspaceId$, workspaceList$, currentWorkspace$, - hasFetchedWorkspaceList$, + initialized$, workspaceEnabled$, renderWorkspaceMenu: jest.fn(), }); diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index 9b386599cea0..b94bf0a17e23 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -3,7 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, combineLatest } from 'rxjs'; +import { isEqual } from 'lodash'; + import { CoreService, WorkspaceAttribute } from '../../types'; import { InternalApplicationStart } from '../application'; import { HttpSetup } from '../http'; @@ -23,7 +25,11 @@ export interface WorkspaceObservables { currentWorkspace$: BehaviorSubject; workspaceList$: BehaviorSubject; workspaceEnabled$: BehaviorSubject; - hasFetchedWorkspaceList$: BehaviorSubject; + initialized$: BehaviorSubject; +} + +enum WORKSPACE_ERROR_REASON_MAP { + WORKSPACE_STALED = 'WORKSPACE_STALED', } /** @@ -41,16 +47,45 @@ export class WorkspaceService implements CoreService(''); private workspaceList$ = new BehaviorSubject([]); private currentWorkspace$ = new BehaviorSubject(null); - private hasFetchedWorkspaceList$ = new BehaviorSubject(false); + private initialized$ = new BehaviorSubject(false); private workspaceEnabled$ = new BehaviorSubject(false); private _renderWorkspaceMenu: WorkspaceMenuRenderFn | null = null; + constructor() { + combineLatest([this.initialized$, this.workspaceList$, this.currentWorkspaceId$]).subscribe( + ([workspaceInitialized, workspaceList, currentWorkspaceId]) => { + if (workspaceInitialized) { + const currentWorkspace = workspaceList.find((w) => w && w.id === currentWorkspaceId); + + /** + * Do a simple idempotent verification here + */ + if (!isEqual(currentWorkspace, this.currentWorkspace$.getValue())) { + this.currentWorkspace$.next(currentWorkspace ?? null); + } + + if (currentWorkspaceId && !currentWorkspace?.id) { + /** + * Current workspace is staled + */ + this.currentWorkspaceId$.error({ + reason: WORKSPACE_ERROR_REASON_MAP.WORKSPACE_STALED, + }); + this.currentWorkspace$.error({ + reason: WORKSPACE_ERROR_REASON_MAP.WORKSPACE_STALED, + }); + } + } + } + ); + } + public setup(): WorkspaceSetup { return { currentWorkspaceId$: this.currentWorkspaceId$, currentWorkspace$: this.currentWorkspace$, workspaceList$: this.workspaceList$, - hasFetchedWorkspaceList$: this.hasFetchedWorkspaceList$, + initialized$: this.initialized$, workspaceEnabled$: this.workspaceEnabled$, registerWorkspaceMenuRender: (render: WorkspaceMenuRenderFn) => (this._renderWorkspaceMenu = render), @@ -68,7 +103,7 @@ export class WorkspaceService implements CoreService public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacePluginSetupDeps) { core.workspaces.workspaceEnabled$.next(true); core.workspaces.registerWorkspaceMenuRender(renderWorkspaceMenu); - + const workspaceClient = new WorkspaceClient(core.http, core.workspaces); - workspaceClient.init(); + await workspaceClient.init(); /** * Retrieve workspace id from url diff --git a/src/plugins/workspace/public/workspace_client.ts b/src/plugins/workspace/public/workspace_client.ts index a43624a5f076..60adc80a3d26 100644 --- a/src/plugins/workspace/public/workspace_client.ts +++ b/src/plugins/workspace/public/workspace_client.ts @@ -2,9 +2,6 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import { combineLatest } from 'rxjs'; -import { isEqual } from 'lodash'; - import { HttpFetchError, HttpFetchOptions, @@ -16,10 +13,6 @@ import { WorkspacePermissionMode } from '../../../core/public'; const WORKSPACES_API_BASE_URL = '/api/workspaces'; -enum WORKSPACE_ERROR_REASON_MAP { - WORKSPACE_STALED = 'WORKSPACE_STALED', -} - const join = (...uriComponents: Array) => uriComponents .filter((comp): comp is string => Boolean(comp)) @@ -67,57 +60,14 @@ export class WorkspaceClient { constructor(http: HttpSetup, workspaces: WorkspaceSetup) { this.http = http; this.workspaces = workspaces; - - combineLatest([ - workspaces.hasFetchedWorkspaceList$, - workspaces.workspaceList$, - workspaces.currentWorkspaceId$, - ]).subscribe(([hasFetchedWorkspaceList, workspaceList, currentWorkspaceId]) => { - if (hasFetchedWorkspaceList) { - const currentWorkspace = this.findWorkspace([workspaceList, currentWorkspaceId]); - - /** - * Do a simple idempotent verification here - */ - if (!isEqual(currentWorkspace, workspaces.currentWorkspace$.getValue())) { - workspaces.currentWorkspace$.next(currentWorkspace); - } - - if (currentWorkspaceId && !currentWorkspace?.id) { - /** - * Current workspace is staled - */ - workspaces.currentWorkspaceId$.error({ - reason: WORKSPACE_ERROR_REASON_MAP.WORKSPACE_STALED, - }); - workspaces.currentWorkspace$.error({ - reason: WORKSPACE_ERROR_REASON_MAP.WORKSPACE_STALED, - }); - } - } - }); } /** * Initialize workspace list */ - public init() { - this.updateWorkspaceListAndNotify(); - } - - private findWorkspace(payload: [WorkspaceAttribute[], string]): WorkspaceAttribute | null { - const [workspaceList, currentWorkspaceId] = payload; - if (!currentWorkspaceId || !workspaceList || !workspaceList.length) { - return null; - } - - const findItem = workspaceList.find((item) => item?.id === currentWorkspaceId); - - if (!findItem) { - return null; - } - - return findItem; + public async init() { + await this.updateWorkspaceList(); + this.workspaces.initialized$.next(true); } /** @@ -155,7 +105,7 @@ export class WorkspaceClient { return [WORKSPACES_API_BASE_URL, join(...path)].filter((item) => item).join('/'); } - private async updateWorkspaceListAndNotify(): Promise { + private async updateWorkspaceList(): Promise { const result = await this.list({ perPage: 999, }); @@ -163,8 +113,6 @@ export class WorkspaceClient { if (result?.success) { this.workspaces.workspaceList$.next(result.result.workspaces); } - - this.workspaces.hasFetchedWorkspaceList$.next(true); } public async enterWorkspace(id: string): Promise> { @@ -234,7 +182,7 @@ export class WorkspaceClient { }); if (result.success) { - this.updateWorkspaceListAndNotify(); + this.updateWorkspaceList(); } return result; @@ -250,7 +198,7 @@ export class WorkspaceClient { const result = await this.safeFetch(this.getPath([id]), { method: 'DELETE' }); if (result.success) { - this.updateWorkspaceListAndNotify(); + this.updateWorkspaceList(); } return result; @@ -325,7 +273,7 @@ export class WorkspaceClient { }); if (result.success) { - this.updateWorkspaceListAndNotify(); + this.updateWorkspaceList(); } return result;