Skip to content

Commit

Permalink
[Workspace]Feat add use cases to workspace form (#6887) (#6967)
Browse files Browse the repository at this point in the history
* Add workspace use case to workspace form



* Remove feature selector in workspace form



* Show use cases in workspace list page



* Change direction for workspace use case selector



* Modify test cases for match use case



* Make use cases as a required field



* Update ui according feedbacks



* Add management feature to dashboards and visualize use cases



* Update latest feature relationships



* Changeset file for PR #6887 created/updated

* Changeset file for PR #6887 created/updated

* Update test case for workspace creator and updater



* Address unit test



* Add discover feature to all use case



* Add missing features to security analytics



* Address PR comments



* Add comment for workspace use cases map



* Update use case UI



* Remove the permissions tab



* Update breadcrum to Create a workspace



* Address ut failed



---------




(cherry picked from commit 7be3e30)

Signed-off-by: Lin Wang <[email protected]>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
Co-authored-by: Yulong Ruan <[email protected]>
  • Loading branch information
4 people authored Jun 7, 2024
1 parent 642e4d3 commit d8b595d
Show file tree
Hide file tree
Showing 25 changed files with 484 additions and 577 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/6887.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- [Workspace]Add use cases to workspace form ([#6887](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6887))
103 changes: 103 additions & 0 deletions src/plugins/workspace/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,106 @@ export const WORKSPACE_APP_CATEGORIES: Record<string, AppCategory> = Object.free
order: 14000,
},
});
/**
*
* This is a temp solution to store relationships between use cases and features.
* The relationship should be provided by plugin itself. The workspace plugin should
* provide some method to register single feature to the use case map instead of
* store a static map in workspace.
*
*/
export const WORKSPACE_USE_CASES = Object.freeze({
observability: {
id: 'observability',
title: i18n.translate('workspace.usecase.observability.title', {
defaultMessage: 'Observability',
}),
description: i18n.translate('workspace.usecase.observability.description', {
defaultMessage:
'Gain visibility into system health, performance, and reliability through monitoring and analysis of logs, metrics, and traces.',
}),
features: [
'discover',
'dashboards',
'visualize',
'maps-dashboards',
'observability-notebooks',
'reports-dashboards',
'integrations',
'alerting',
'anomaly-detection-dashboards',
'observability-metrics',
'observability-traces',
'observability-applications',
// Add management avoid index patterns application not found for dashboards or visualize
'management',
] as string[],
},
'security-analytics': {
id: 'security-analytics',
title: i18n.translate('workspace.usecase.security.analytics.title', {
defaultMessage: 'Security Analytics',
}),
description: i18n.translate('workspace.usecase.analytics.description', {
defaultMessage:
'Detect and investigate potential security threats and vulnerabilities across your systems and data.',
}),
features: [
'discover',
'dashboards',
'visualize',
'maps-dashboards',
'observability-notebooks',
'reports-dashboards',
'integrations',
'alerting',
'anomaly-detection-dashboards',
'opensearch_security_analytics_dashboards',
// Add management avoid index patterns application not found for dashboards or visualize
'management',
] as string[],
},
analytics: {
id: 'analytics',
title: i18n.translate('workspace.usecase.analytics.title', {
defaultMessage: 'Analytics',
}),
description: i18n.translate('workspace.usecase.analytics.description', {
defaultMessage:
'Analyze data to derive insights, identify patterns and trends, and make data-driven decisions.',
}),
features: [
'discover',
'dashboards',
'visualize',
'maps-dashboards',
'observability-notebooks',
'reports-dashboards',
'integrations',
'alerting',
'anomaly-detection-dashboards',
// Add management avoid index patterns application not found for dashboards or visualize
'management',
] as string[],
},
search: {
id: 'search',
title: i18n.translate('workspace.usecase.search.title', {
defaultMessage: 'Search',
}),
description: i18n.translate('workspace.usecase.search.description', {
defaultMessage:
"Quickly find and explore relevant information across your organization's data sources.",
}),
features: [
'discover',
'dashboards',
'visualize',
'maps-dashboards',
'reports-dashboards',
'searchRelevance',
// Add management avoid index patterns application not found for dashboards or visualize
'management',
] as string[],
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ const navigateToApp = jest.fn();
const notificationToastsAddSuccess = jest.fn();
const notificationToastsAddDanger = jest.fn();
const PublicAPPInfoMap = new Map([
['app1', { id: 'app1', title: 'app1' }],
['app2', { id: 'app2', title: 'app2', category: { id: 'category1', label: 'category1' } }],
['app3', { id: 'app3', category: { id: 'category1', label: 'category1' } }],
['app4', { id: 'app4', category: { id: 'category2', label: 'category2' } }],
['app5', { id: 'app5', category: { id: 'category2', label: 'category2' } }],
['data-explorer', { id: 'data-explorer', title: 'Data Explorer' }],
['dashboards', { id: 'dashboards', title: 'Dashboards' }],
]);

const mockCoreStart = coreMock.createStart();
Expand Down Expand Up @@ -116,6 +113,22 @@ describe('WorkspaceCreator', () => {
expect(workspaceClientCreate).not.toHaveBeenCalled();
});

it('should not create workspace without use cases', async () => {
setHrefSpy.mockReset();
const { getByTestId } = render(
<WorkspaceCreator
workspaceConfigurableApps$={new BehaviorSubject([...PublicAPPInfoMap.values()])}
/>
);
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
});
expect(setHrefSpy).not.toHaveBeenCalled();
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).not.toHaveBeenCalled();
});

it('cancel create workspace', async () => {
const { findByText, getByTestId } = render(
<WorkspaceCreator
Expand Down Expand Up @@ -148,12 +161,14 @@ describe('WorkspaceCreator', () => {
fireEvent.input(colorSelector, {
target: { value: '#000000' },
});
fireEvent.click(getByTestId('workspaceUseCase-observability'));
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).toHaveBeenCalledWith(
expect.objectContaining({
name: 'test workspace name',
color: '#000000',
description: 'test workspace description',
features: expect.arrayContaining(['use-case-observability']),
}),
undefined
);
Expand All @@ -163,37 +178,6 @@ describe('WorkspaceCreator', () => {
expect(notificationToastsAddDanger).not.toHaveBeenCalled();
});

it('create workspace with customized features', async () => {
setHrefSpy.mockReset();
const { getByTestId } = render(
<WorkspaceCreator
workspaceConfigurableApps$={new BehaviorSubject([...PublicAPPInfoMap.values()])}
/>
);
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
});
fireEvent.click(getByTestId('workspaceForm-workspaceFeatureVisibility-app1'));
fireEvent.click(getByTestId('workspaceForm-workspaceFeatureVisibility-category1'));
expect(setHrefSpy).not.toHaveBeenCalled();
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).toHaveBeenCalledWith(
expect.objectContaining({
name: 'test workspace name',
features: expect.arrayContaining(['app1', 'app2', 'app3']),
}),
undefined
);
await waitFor(() => {
expect(notificationToastsAddSuccess).toHaveBeenCalled();
});
expect(notificationToastsAddDanger).not.toHaveBeenCalled();
await waitFor(() => {
expect(setHrefSpy).toHaveBeenCalledWith(expect.stringMatching(/workspace_overview$/));
});
});

it('should show danger toasts after create workspace failed', async () => {
workspaceClientCreate.mockReturnValueOnce({ result: { id: 'failResult' }, success: false });
const { getByTestId } = render(
Expand All @@ -205,6 +189,7 @@ describe('WorkspaceCreator', () => {
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
});
fireEvent.click(getByTestId('workspaceUseCase-observability'));
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).toHaveBeenCalled();
await waitFor(() => {
Expand All @@ -226,6 +211,7 @@ describe('WorkspaceCreator', () => {
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
});
fireEvent.click(getByTestId('workspaceUseCase-observability'));
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).toHaveBeenCalled();
await waitFor(() => {
Expand All @@ -235,12 +221,16 @@ describe('WorkspaceCreator', () => {
});

it('create workspace with customized permissions', async () => {
const { getByTestId, getByText, getAllByText } = render(<WorkspaceCreator />);
const { getByTestId, getAllByText } = render(
<WorkspaceCreator
workspaceConfigurableApps$={new BehaviorSubject([...PublicAPPInfoMap.values()])}
/>
);
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
});
fireEvent.click(getByText('Users & Permissions'));
fireEvent.click(getByTestId('workspaceUseCase-observability'));
fireEvent.click(getByTestId('workspaceForm-permissionSettingPanel-user-addNew'));
const userIdInput = getAllByText('Select')[0];
fireEvent.click(userIdInput);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
return (
<EuiPage paddingSize="none">
<EuiPageBody>
<EuiPageHeader restrictWidth pageTitle="Create Workspace" />
<EuiPageHeader restrictWidth pageTitle="Create a workspace" />
<EuiSpacer />
<EuiPageContent
verticalPosition="center"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const WorkspaceCreatorApp = (props: WorkspaceCreatorProps) => {
chrome?.setBreadcrumbs([
{
text: i18n.translate('workspace.workspaceCreateTitle', {
defaultMessage: 'Create workspace',
defaultMessage: 'Create a workspace',
}),
},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ export enum WorkspaceOperationType {
Update = 'update',
}

export enum WorkspaceFormTabs {
NotSelected,
FeatureVisibility,
UsersAndPermissions,
}

export enum WorkspacePermissionItemType {
User = 'user',
Group = 'group',
Expand Down
10 changes: 0 additions & 10 deletions src/plugins/workspace/public/components/workspace_form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,6 @@ export interface WorkspaceFormData extends WorkspaceFormSubmitData {
reserved?: boolean;
}

export interface WorkspaceFeature {
id: string;
name: string;
}

export interface WorkspaceFeatureGroup {
name: string;
features: WorkspaceFeature[];
}

export type WorkspaceFormErrors = {
[key in keyof Omit<WorkspaceFormData, 'permissionSettings'>]?: string;
} & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,35 @@ describe('useWorkspaceForm', () => {
act(() => {
renderResult.result.current.handleFormSubmit({ preventDefault: jest.fn() });
});
expect(renderResult.result.current.formErrors).toEqual({
name: 'Invalid workspace name',
expect(renderResult.result.current.formErrors).toEqual(
expect.objectContaining({
name: 'Invalid workspace name',
})
);
expect(onSubmitMock).not.toHaveBeenCalled();
});
it('should return "Use case is required. Select a use case." and not call onSubmit', async () => {
const { renderResult, onSubmitMock } = setup({
id: 'foo',
name: 'test-workspace-name',
});
expect(renderResult.result.current.formErrors).toEqual({});

act(() => {
renderResult.result.current.handleFormSubmit({ preventDefault: jest.fn() });
});
expect(renderResult.result.current.formErrors).toEqual(
expect.objectContaining({
features: 'Use case is required. Select a use case.',
})
);
expect(onSubmitMock).not.toHaveBeenCalled();
});
it('should call onSubmit with workspace name and features', async () => {
const { renderResult, onSubmitMock } = setup({
id: 'foo',
name: 'test-workspace-name',
features: ['use-case-observability'],
});
expect(renderResult.result.current.formErrors).toEqual({});

Expand All @@ -53,7 +73,7 @@ describe('useWorkspaceForm', () => {
expect(onSubmitMock).toHaveBeenCalledWith(
expect.objectContaining({
name: 'test-workspace-name',
features: ['workspace_update', 'workspace_overview'],
features: ['use-case-observability', 'workspace_update', 'workspace_overview'],
})
);
});
Expand Down
Loading

0 comments on commit d8b595d

Please sign in to comment.