diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap
index 1183c4cccd68..f074f9715c99 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap
@@ -257,6 +257,7 @@ exports[`SavedObjectsTable should render normally 1`] = `
"has": [MockFunction],
}
}
+ availableWorkspaces={Array []}
basePath={
BasePath {
"basePath": "",
@@ -276,6 +277,7 @@ exports[`SavedObjectsTable should render normally 1`] = `
"has": [MockFunction],
}
}
+ currentWorkspaceId=""
filters={
Array [
Object {
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx
index 7e5bb318f4d0..3cc6d04cfac2 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx
@@ -37,6 +37,7 @@ import { actionServiceMock } from '../../../services/action_service.mock';
import { columnServiceMock } from '../../../services/column_service.mock';
import { SavedObjectsManagementAction } from '../../..';
import { Table, TableProps } from './table';
+import { WorkspaceAttribute } from 'opensearch-dashboards/public';
const defaultProps: TableProps = {
basePath: httpServiceMock.createSetupContract().basePath,
@@ -115,6 +116,52 @@ describe('Table', () => {
expect(component).toMatchSnapshot();
});
+ it('should render gotoApp link correctly for workspace', () => {
+ const item = {
+ id: 'dashboard-1',
+ type: 'dashboard',
+ workspaces: ['ws-1'],
+ attributes: {},
+ references: [],
+ meta: {
+ title: `My-Dashboard-test`,
+ icon: 'indexPatternApp',
+ editUrl: '/management/opensearch-dashboards/objects/savedDashboards/dashboard-1',
+ inAppUrl: {
+ path: '/app/dashboards#/view/dashboard-1',
+ uiCapabilitiesPath: 'dashboard.show',
+ },
+ },
+ };
+ const props = {
+ ...defaultProps,
+ availableWorkspaces: [{ id: 'ws-1', name: 'My workspace' } as WorkspaceAttribute],
+ items: [item],
+ };
+ // not in a workspace
+ let component = shallowWithI18nProvider(
);
+
+ let table = component.find('EuiBasicTable');
+ let columns = table.prop<
+ Array<{ render: (id: string, record: unknown) => React.ReactElement }>
+ >('columns');
+ let content = columns[1].render('My-Dashboard-test', item);
+ expect(content.props.href).toEqual('http://localhost/w/ws-1/app/dashboards#/view/dashboard-1');
+
+ // in a workspace
+ const currentWorkspaceId = 'foo-ws';
+ component = shallowWithI18nProvider(
+
+ );
+
+ table = component.find('EuiBasicTable');
+ columns = table.prop('columns');
+ content = columns[1].render('My-Dashboard-test', item);
+ expect(content.props.href).toEqual(
+ `http://localhost/w/${currentWorkspaceId}/app/dashboards#/view/dashboard-1`
+ );
+ });
+
it('should handle query parse error', () => {
const onQueryChangeMock = jest.fn();
const customizedProps = {
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx
index 636933d449df..56606711db98 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx
@@ -28,7 +28,7 @@
* under the License.
*/
-import { IBasePath } from 'src/core/public';
+import { IBasePath, WorkspaceAttribute } from 'src/core/public';
import React, { PureComponent, Fragment } from 'react';
import moment from 'moment';
import {
@@ -56,6 +56,7 @@ import {
SavedObjectsManagementAction,
SavedObjectsManagementColumnServiceStart,
} from '../../../services';
+import { formatUrlWithWorkspaceId } from '../../../../../../core/public/utils';
export interface TableProps {
basePath: IBasePath;
@@ -83,6 +84,8 @@ export interface TableProps {
onShowRelationships: (object: SavedObjectWithMetadata) => void;
canGoInApp: (obj: SavedObjectWithMetadata) => boolean;
dateFormat: string;
+ availableWorkspaces?: WorkspaceAttribute[];
+ currentWorkspaceId?: string;
}
interface TableState {
@@ -177,8 +180,12 @@ export class Table extends PureComponent {
columnRegistry,
namespaceRegistry,
dateFormat,
+ availableWorkspaces,
+ currentWorkspaceId,
} = this.props;
+ const visibleWsIds = availableWorkspaces?.map((ws) => ws.id) || [];
+
const pagination = {
pageIndex,
pageSize,
@@ -231,9 +238,19 @@ export class Table extends PureComponent {
if (!canGoInApp) {
return {title || getDefaultTitle(object)};
}
- return (
- {title || getDefaultTitle(object)}
- );
+ let inAppUrl = basePath.prepend(path);
+ if (object.workspaces?.length) {
+ if (currentWorkspaceId) {
+ inAppUrl = formatUrlWithWorkspaceId(path, currentWorkspaceId, basePath);
+ } else {
+ // find first workspace user have permission
+ const workspaceId = object.workspaces.find((wsId) => visibleWsIds.includes(wsId));
+ if (workspaceId) {
+ inAppUrl = formatUrlWithWorkspaceId(path, workspaceId, basePath);
+ }
+ }
+ }
+ return {title || getDefaultTitle(object)};
},
} as EuiTableFieldDataColumnType>,
{
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx
index 5a6bf0713d95..74ae23c34dcb 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx
@@ -48,6 +48,7 @@ import {
notificationServiceMock,
savedObjectsServiceMock,
applicationServiceMock,
+ workspacesServiceMock,
} from '../../../../../core/public/mocks';
import { dataPluginMock } from '../../../../data/public/mocks';
import { serviceRegistryMock } from '../../services/service_registry.mock';
@@ -102,6 +103,7 @@ describe('SavedObjectsTable', () => {
let notifications: ReturnType;
let savedObjects: ReturnType;
let search: ReturnType['search'];
+ let workspaces: ReturnType;
const shallowRender = (overrides: Partial = {}) => {
return (shallowWithI18nProvider(
@@ -121,6 +123,7 @@ describe('SavedObjectsTable', () => {
notifications = notificationServiceMock.createStartContract();
savedObjects = savedObjectsServiceMock.createStartContract();
search = dataPluginMock.createStartContract().search;
+ workspaces = workspacesServiceMock.createStartContract();
const applications = applicationServiceMock.createStartContract();
applications.capabilities = {
@@ -161,6 +164,7 @@ describe('SavedObjectsTable', () => {
goInspectObject: () => {},
canGoInApp: () => true,
search,
+ workspaces,
};
findObjectsMock.mockImplementation(() => ({
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 955482cc0676..127ed08423e3 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
@@ -65,7 +65,10 @@ import {
OverlayStart,
NotificationsStart,
ApplicationStart,
+ WorkspacesStart,
+ WorkspaceAttribute,
} from 'src/core/public';
+import { Subscription } from 'rxjs';
import { RedirectAppLinks } from '../../../../opensearch_dashboards_react/public';
import { IndexPatternsContract } from '../../../../data/public';
import {
@@ -110,6 +113,7 @@ export interface SavedObjectsTableProps {
overlays: OverlayStart;
notifications: NotificationsStart;
applications: ApplicationStart;
+ workspaces: WorkspacesStart;
perPageConfig: number;
goInspectObject: (obj: SavedObjectWithMetadata) => void;
canGoInApp: (obj: SavedObjectWithMetadata) => boolean;
@@ -137,10 +141,13 @@ export interface SavedObjectsTableState {
exportAllOptions: ExportAllOption[];
exportAllSelectedOptions: Record;
isIncludeReferencesDeepChecked: boolean;
+ currentWorkspaceId?: string;
+ availableWorkspaces?: WorkspaceAttribute[];
}
-
export class SavedObjectsTable extends Component {
private _isMounted = false;
+ private currentWorkspaceIdSubscription?: Subscription;
+ private workspacesSubscription?: Subscription;
constructor(props: SavedObjectsTableProps) {
super(props);
@@ -172,6 +179,7 @@ export class SavedObjectsTable extends Component {
@@ -246,6 +255,24 @@ export class SavedObjectsTable extends Component {
+ const workspace = this.props.workspaces;
+ this.currentWorkspaceIdSubscription = workspace.currentWorkspaceId$.subscribe((workspaceId) =>
+ this.setState({
+ currentWorkspaceId: workspaceId,
+ })
+ );
+
+ this.workspacesSubscription = workspace.workspaceList$.subscribe((workspaceList) => {
+ this.setState({ availableWorkspaces: workspaceList });
+ });
+ };
+
+ unSubscribeWorkspace = () => {
+ this.currentWorkspaceIdSubscription?.unsubscribe();
+ this.workspacesSubscription?.unsubscribe();
+ };
+
fetchSavedObject = (type: string, id: string) => {
this.setState({ isSearching: true }, () => this.debouncedFetchObject(type, id));
};
@@ -802,6 +829,8 @@ export class SavedObjectsTable extends Component
diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx
index b3ef976d8283..42889d6d8095 100644
--- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx
@@ -92,6 +92,7 @@ const SavedObjectsTablePage = ({
overlays={coreStart.overlays}
notifications={coreStart.notifications}
applications={coreStart.application}
+ workspaces={coreStart.workspaces}
perPageConfig={itemsPerPage}
goInspectObject={(savedObject) => {
const { editUrl } = savedObject.meta;
diff --git a/src/plugins/workspace/opensearch_dashboards.json b/src/plugins/workspace/opensearch_dashboards.json
index efb5cef5fdbe..e6ff1e2d0173 100644
--- a/src/plugins/workspace/opensearch_dashboards.json
+++ b/src/plugins/workspace/opensearch_dashboards.json
@@ -7,6 +7,6 @@
"savedObjects",
"opensearchDashboardsReact"
],
- "optionalPlugins": [],
+ "optionalPlugins": ["savedObjectsManagement"],
"requiredBundles": ["opensearchDashboardsReact"]
}
diff --git a/src/plugins/workspace/public/components/workspace_column/index.ts b/src/plugins/workspace/public/components/workspace_column/index.ts
new file mode 100644
index 000000000000..a9325eb49279
--- /dev/null
+++ b/src/plugins/workspace/public/components/workspace_column/index.ts
@@ -0,0 +1,6 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { getWorkspaceColumn, WorkspaceColumn } from './workspace_column';
diff --git a/src/plugins/workspace/public/components/workspace_column/workspace_colum.test.tsx b/src/plugins/workspace/public/components/workspace_column/workspace_colum.test.tsx
new file mode 100644
index 000000000000..655bd9e57f2c
--- /dev/null
+++ b/src/plugins/workspace/public/components/workspace_column/workspace_colum.test.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import React from 'react';
+import { coreMock } from '../../../../../core/public/mocks';
+import { render } from '@testing-library/react';
+import { WorkspaceColumn } from './workspace_column';
+
+describe('workspace column in saved objects page', () => {
+ const coreSetup = coreMock.createSetup();
+ const workspaceList = [
+ {
+ id: 'ws-1',
+ name: 'foo',
+ },
+ {
+ id: 'ws-2',
+ name: 'bar',
+ },
+ ];
+ coreSetup.workspaces.workspaceList$.next(workspaceList);
+
+ it('should show workspace name correctly', () => {
+ const workspaces = ['ws-1', 'ws-2'];
+ const { container } = render();
+ expect(container).toMatchInlineSnapshot(`
+
+ `);
+ });
+
+ it('show empty when no workspace', () => {
+ const { container } = render();
+ expect(container).toMatchInlineSnapshot(`
+
+ `);
+ });
+
+ it('show empty when workspace can not found', () => {
+ const { container } = render();
+ expect(container).toMatchInlineSnapshot(`
+
+ `);
+ });
+});
diff --git a/src/plugins/workspace/public/components/workspace_column/workspace_column.tsx b/src/plugins/workspace/public/components/workspace_column/workspace_column.tsx
new file mode 100644
index 000000000000..3d964009ee86
--- /dev/null
+++ b/src/plugins/workspace/public/components/workspace_column/workspace_column.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { EuiText } from '@elastic/eui';
+import useObservable from 'react-use/lib/useObservable';
+import { i18n } from '@osd/i18n';
+import { WorkspaceAttribute, CoreSetup } from '../../../../../core/public';
+import { SavedObjectsManagementColumn } from '../../../../saved_objects_management/public';
+
+interface WorkspaceColumnProps {
+ coreSetup: CoreSetup;
+ workspaces?: string[];
+}
+
+export function WorkspaceColumn({ coreSetup, workspaces }: WorkspaceColumnProps) {
+ const workspaceList = useObservable(coreSetup.workspaces.workspaceList$);
+
+ const wsLookUp = workspaceList?.reduce((map, ws) => {
+ return map.set(ws.id, ws.name);
+ }, new Map());
+
+ const workspaceNames = workspaces?.map((wsId) => wsLookUp?.get(wsId)).join(' | ');
+
+ return {workspaceNames};
+}
+
+export function getWorkspaceColumn(
+ coreSetup: CoreSetup
+): SavedObjectsManagementColumn {
+ return {
+ id: 'workspace_column',
+ euiColumn: {
+ align: 'left',
+ field: 'workspaces',
+ name: i18n.translate('savedObjectsManagement.objectsTable.table.columnWorkspacesName', {
+ defaultMessage: 'Workspaces',
+ }),
+ render: (workspaces: string[]) => {
+ return ;
+ },
+ },
+ loadData: () => {
+ return Promise.resolve(undefined);
+ },
+ };
+}
diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts
index 497b41be520a..b2c1984abac2 100644
--- a/src/plugins/workspace/public/plugin.test.ts
+++ b/src/plugins/workspace/public/plugin.test.ts
@@ -9,6 +9,7 @@ import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.moc
import { applicationServiceMock, chromeServiceMock, coreMock } from '../../../core/public/mocks';
import { WorkspacePlugin } from './plugin';
import { WORKSPACE_FATAL_ERROR_APP_ID, WORKSPACE_OVERVIEW_APP_ID } from '../common/constants';
+import { savedObjectsManagementPluginMock } from '../../saved_objects_management/public/mocks';
describe('Workspace plugin', () => {
const getSetupMock = () => ({
@@ -20,17 +21,21 @@ describe('Workspace plugin', () => {
});
it('#setup', async () => {
const setupMock = getSetupMock();
+ const savedObjectManagementSetupMock = savedObjectsManagementPluginMock.createSetupContract();
const workspacePlugin = new WorkspacePlugin();
- await workspacePlugin.setup(setupMock);
+ await workspacePlugin.setup(setupMock, {
+ savedObjectsManagement: savedObjectManagementSetupMock,
+ });
expect(setupMock.application.register).toBeCalledTimes(3);
expect(WorkspaceClientMock).toBeCalledTimes(1);
+ expect(savedObjectManagementSetupMock.columns.register).toBeCalledTimes(1);
});
it('#call savedObjectsClient.setCurrentWorkspace when current workspace id changed', async () => {
const workspacePlugin = new WorkspacePlugin();
const setupMock = getSetupMock();
const coreStart = coreMock.createStart();
- await workspacePlugin.setup(setupMock);
+ await workspacePlugin.setup(setupMock, {});
workspacePlugin.start(coreStart);
coreStart.workspaces.currentWorkspaceId$.next('foo');
expect(coreStart.savedObjects.client.setCurrentWorkspace).toHaveBeenCalledWith('foo');
@@ -68,7 +73,7 @@ describe('Workspace plugin', () => {
});
const workspacePlugin = new WorkspacePlugin();
- await workspacePlugin.setup(setupMock);
+ await workspacePlugin.setup(setupMock, {});
expect(setupMock.application.register).toBeCalledTimes(3);
expect(WorkspaceClientMock).toBeCalledTimes(1);
expect(workspaceClientMock.enterWorkspace).toBeCalledWith('workspaceId');
@@ -122,7 +127,7 @@ describe('Workspace plugin', () => {
});
const workspacePlugin = new WorkspacePlugin();
- await workspacePlugin.setup(setupMock);
+ await workspacePlugin.setup(setupMock, {});
currentAppIdSubscriber?.next(WORKSPACE_FATAL_ERROR_APP_ID);
expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_OVERVIEW_APP_ID);
windowSpy.mockRestore();
@@ -131,7 +136,7 @@ describe('Workspace plugin', () => {
it('#setup register workspace dropdown menu when setup', async () => {
const setupMock = coreMock.createSetup();
const workspacePlugin = new WorkspacePlugin();
- await workspacePlugin.setup(setupMock);
+ await workspacePlugin.setup(setupMock, {});
expect(setupMock.chrome.registerCollapsibleNavHeader).toBeCalledTimes(1);
});
});
diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts
index bce606583b85..c28bc2f1cb1a 100644
--- a/src/plugins/workspace/public/plugin.ts
+++ b/src/plugins/workspace/public/plugin.ts
@@ -22,11 +22,17 @@ import {
import { getWorkspaceIdFromUrl } from '../../../core/public/utils';
import { Services } from './types';
import { WorkspaceClient } from './workspace_client';
+import { SavedObjectsManagementPluginSetup } from '../../../plugins/saved_objects_management/public';
import { WorkspaceMenu } from './components/workspace_menu/workspace_menu';
+import { getWorkspaceColumn } from './components/workspace_column';
type WorkspaceAppType = (params: AppMountParameters, services: Services) => () => void;
-export class WorkspacePlugin implements Plugin<{}, {}, {}> {
+interface WorkspacePluginSetupDeps {
+ savedObjectsManagement?: SavedObjectsManagementPluginSetup;
+}
+
+export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> {
private coreStart?: CoreStart;
private currentWorkspaceSubscription?: Subscription;
private _changeSavedObjectCurrentWorkspace() {
@@ -39,7 +45,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, {}> {
}
}
- public async setup(core: CoreSetup) {
+ public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacePluginSetupDeps) {
const workspaceClient = new WorkspaceClient(core.http, core.workspaces);
await workspaceClient.init();
@@ -140,6 +146,11 @@ export class WorkspacePlugin implements Plugin<{}, {}, {}> {
},
});
+ /**
+ * register workspace column into saved objects table
+ */
+ savedObjectsManagement?.columns.register(getWorkspaceColumn(core));
+
return {};
}