Skip to content

Commit

Permalink
Add workspace filter into saved objects page (opensearch-project#211)
Browse files Browse the repository at this point in the history
* Add workspace column/filter into saved objects page

Signed-off-by: Hailong Cui <ihailong@amazon.com>

fix failed test case

Signed-off-by: Hailong Cui <ihailong@amazon.com>

move workspace column to its own folder

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* default workspace

Signed-off-by: Hailong Cui <ihailong@amazon.com>

fix test case

Signed-off-by: Hailong Cui <ihailong@amazon.com>

add test case

Signed-off-by: Hailong Cui <ihailong@amazon.com>

remove hide import

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* address review comments

Signed-off-by: Hailong Cui <ihailong@amazon.com>

---------

Signed-off-by: Hailong Cui <ihailong@amazon.com>
  • Loading branch information
Hailong-am committed Apr 15, 2024
1 parent 476cffc commit bafb405
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,4 +359,4 @@ export { __osdBootstrap__ } from './osd_bootstrap';

export { WorkspacesStart, WorkspacesSetup, WorkspacesService, WorkspaceObject } from './workspace';

export { debounce } from './utils';
export { debounce, DEFAULT_WORKSPACE_ID } from './utils';
2 changes: 1 addition & 1 deletion src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export {
} from './metrics';

export { AppCategory, WorkspaceAttribute } from '../types';
export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE } from '../utils';
export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE, DEFAULT_WORKSPACE_ID } from '../utils';

export {
SavedObject,
Expand Down
6 changes: 6 additions & 0 deletions src/core/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@
export const WORKSPACE_TYPE = 'workspace';

export const WORKSPACE_PATH_PREFIX = '/w';

/**
* deafult workspace is a virtual workspace,
* saved objects without any workspaces are consider belongs to default workspace
*/
export const DEFAULT_WORKSPACE_ID = 'default';
7 changes: 6 additions & 1 deletion src/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@ export {
IContextProvider,
} from './context';
export { DEFAULT_APP_CATEGORIES } from './default_app_categories';
export { WORKSPACE_PATH_PREFIX, WORKSPACE_TYPE } from './constants';
export { getWorkspaceIdFromUrl, formatUrlWithWorkspaceId, cleanWorkspaceId } from './workspace';
export {
WORKSPACE_PATH_PREFIX,
PUBLIC_WORKSPACE_ID,
WORKSPACE_TYPE,
DEFAULT_WORKSPACE_ID,
} from './constants';
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export interface SavedObjectCountOptions {
export async function getSavedObjectCounts(
http: HttpStart,
options: SavedObjectCountOptions
): Promise<Record<string, number>> {
return await http.post<Record<string, number>>(
): Promise<Record<string, Record<string, number>>> {
return await http.post<Record<string, Record<string, number>>>(
`/api/opensearch-dashboards/management/saved_objects/scroll/counts`,
{ body: JSON.stringify(options) }
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ describe('getQueryText', () => {
return [{ value: 'lala' }, { value: 'lolo' }];
} else if (field === 'namespaces') {
return [{ value: 'default' }];
} else if (field === 'workspaces') {
return [{ value: 'workspaces' }];
}
return [];
},
Expand All @@ -47,6 +49,7 @@ describe('getQueryText', () => {
queryText: 'foo bar',
visibleTypes: 'lala',
visibleNamespaces: 'default',
visibleWorkspaces: 'workspaces',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ import { Query } from '@elastic/eui';
interface ParsedQuery {
queryText?: string;
visibleTypes?: string[];
visibleNamespaces?: string[];
visibleWorkspaces?: string[];
}

export function parseQuery(query: Query): ParsedQuery {
let queryText: string | undefined;
let visibleTypes: string[] | undefined;
let visibleNamespaces: string[] | undefined;
let visibleWorkspaces: string[] | undefined;

if (query) {
if (query.ast.getTermClauses().length) {
Expand All @@ -53,11 +56,15 @@ export function parseQuery(query: Query): ParsedQuery {
if (query.ast.getFieldClauses('namespaces')) {
visibleNamespaces = query.ast.getFieldClauses('namespaces')[0].value as string[];
}
if (query.ast.getFieldClauses('workspaces')) {
visibleWorkspaces = query.ast.getFieldClauses('workspaces')[0].value as string[];
}
}

return {
queryText,
visibleTypes,
visibleNamespaces,
visibleWorkspaces,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {

import React from 'react';
import { Query } from '@elastic/eui';
import { waitFor } from '@testing-library/dom';
import { ShallowWrapper } from 'enzyme';
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers';
import {
Expand All @@ -62,6 +63,9 @@ import {
} from './saved_objects_table';
import { Flyout, Relationships } from './components';
import { SavedObjectWithMetadata } from '../../types';
import { WorkspaceObject } from 'opensearch-dashboards/public';
import { DEFAULT_WORKSPACE_ID } from '../../../../../core/public';
import { TableProps } from './components/table';

const allowedTypes = ['index-pattern', 'visualization', 'dashboard', 'search'];

Expand Down Expand Up @@ -576,4 +580,194 @@ describe('SavedObjectsTable', () => {
expect(component.state('selectedSavedObjects').length).toBe(0);
});
});

describe('workspace filter', () => {
it('show workspace filter when workspace turn on and not in any workspace', async () => {
const applications = applicationServiceMock.createStartContract();
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: false,
},
workspaces: {
enabled: true,
},
};

const workspaceList: WorkspaceObject[] = [
{
id: 'workspace1',
name: 'foo',
},
{
id: 'workspace2',
name: 'bar',
},
];
workspaces.workspaceList$.next(workspaceList);
workspaces.currentWorkspaceId$.next('');
workspaces.currentWorkspace$.next(null);

const component = shallowRender({ applications, workspaces });

// Ensure all promises resolve
await new Promise((resolve) => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();

const props = component.find('Table').props() as TableProps;
const filters = props.filters;
expect(filters.length).toBe(2);
expect(filters[0].field).toBe('type');
expect(filters[1].field).toBe('workspaces');
expect(filters[1].options.length).toBe(3);
expect(filters[1].options[0].value).toBe('foo');
expect(filters[1].options[1].value).toBe('bar');
expect(filters[1].options[2].value).toBe(DEFAULT_WORKSPACE_ID);
});

it('show workspace filter when workspace turn on and enter a workspace', async () => {
const applications = applicationServiceMock.createStartContract();
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: false,
},
workspaces: {
enabled: true,
},
};

const workspaceList: WorkspaceObject[] = [
{
id: 'workspace1',
name: 'foo',
},
{
id: 'workspace2',
name: 'bar',
},
];
workspaces.workspaceList$.next(workspaceList);
workspaces.currentWorkspaceId$.next('workspace1');
workspaces.currentWorkspace$.next(workspaceList[0]);

const component = shallowRender({ applications, workspaces });

// Ensure all promises resolve
await new Promise((resolve) => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();

const props = component.find('Table').props() as TableProps;
const filters = props.filters;
const wsFilter = filters.filter((f) => f.field === 'workspaces');
expect(wsFilter.length).toBe(1);
expect(wsFilter[0].options.length).toBe(1);
expect(wsFilter[0].options[0].value).toBe('foo');
});

it('workspace exists in find options when workspace on', async () => {
findObjectsMock.mockClear();
const applications = applicationServiceMock.createStartContract();
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: false,
},
workspaces: {
enabled: true,
},
};

const workspaceList: WorkspaceObject[] = [
{
id: 'workspace1',
name: 'foo',
},
{
id: 'workspace2',
name: 'bar',
},
];
workspaces.workspaceList$.next(workspaceList);
workspaces.currentWorkspaceId$.next('workspace1');
workspaces.currentWorkspace$.next(workspaceList[0]);

const component = shallowRender({ applications, workspaces });

// Ensure all promises resolve
await new Promise((resolve) => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();

await waitFor(() => {
expect(findObjectsMock).toBeCalledWith(
http,
expect.objectContaining({
workspaces: expect.arrayContaining(['workspace1']),
})
);
});
});

it('workspace exists in find options when workspace on and not in any workspace', async () => {
findObjectsMock.mockClear();
const applications = applicationServiceMock.createStartContract();
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: false,
},
workspaces: {
enabled: true,
},
};

const workspaceList: WorkspaceObject[] = [
{
id: 'workspace1',
name: 'foo',
},
{
id: 'workspace2',
name: 'bar',
},
];
workspaces.workspaceList$.next(workspaceList);

const component = shallowRender({ applications, workspaces });

// Ensure all promises resolve
await new Promise((resolve) => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();

await waitFor(() => {
expect(findObjectsMock).toBeCalledWith(
http,
expect.objectContaining({
workspaces: expect.arrayContaining(['workspace1', 'default']),
workspacesSearchOperator: expect.stringMatching('OR'),
})
);
});
});
});
});
Loading

0 comments on commit bafb405

Please sign in to comment.