Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] [Workspace]Feat add use cases to workspace form #6967

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading