Skip to content

Commit

Permalink
[Workspaces]Add features in use case card and preselect first use case (
Browse files Browse the repository at this point in the history
#7703)

* Display features in workspace use case card

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Changeset file for PR #7703 created/updated

* Changeset file for PR #7703 created/updated

* Changeset file for PR #7703 created/updated

* Add more test cases

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Update snapshort of workspace list

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Address pr comments

Signed-off-by: Lin Wang <wonglam@amazon.com>

---------

Signed-off-by: Lin Wang <wonglam@amazon.com>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
  • Loading branch information
wanglam and opensearch-changeset-bot[bot] committed Aug 22, 2024
1 parent 3a41f7b commit 446aa08
Show file tree
Hide file tree
Showing 25 changed files with 661 additions and 183 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/7703.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- [Workspaces]Add features in use case card and preselect first use case ([#7703](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7703))
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,17 @@
*/

import React from 'react';
import { IntlProvider } from 'react-intl';
import { render, screen, fireEvent } from '@testing-library/react';
import { coreMock } from '../../../../../core/public/mocks';
import { createMockedRegisteredUseCases$ } from '../../mocks';

import { UseCaseFooter as UseCaseFooterComponent, UseCaseFooterProps } from './use_case_footer';
import { coreMock, httpServiceMock } from '../../../../../core/public/mocks';
import { IntlProvider } from 'react-intl';
import { WorkspaceUseCase } from '../../types';
import { CoreStart } from 'opensearch-dashboards/public';
import { BehaviorSubject } from 'rxjs';
import { WORKSPACE_USE_CASES } from '../../../common/constants';

describe('UseCaseFooter', () => {
// let coreStartMock: CoreStart;
const navigateToApp = jest.fn();
const registeredUseCases$ = new BehaviorSubject([
WORKSPACE_USE_CASES.observability,
WORKSPACE_USE_CASES['security-analytics'],
WORKSPACE_USE_CASES.essentials,
WORKSPACE_USE_CASES.search,
]);
const registeredUseCases$ = createMockedRegisteredUseCases$();

const getMockCore = (isDashboardAdmin: boolean = true) => {
const coreStartMock = coreMock.createStart();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const UseCaseFooter = ({
const closePopover = () => setPopover(false);

const appId =
availableUseCases?.find((useCase) => useCase.id === useCaseId)?.features[0] ??
availableUseCases?.find((useCase) => useCase.id === useCaseId)?.features[0].id ??
WORKSPACE_DETAIL_APP_ID;

const filterWorkspaces = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@

import React from 'react';
import { PublicAppInfo } from 'opensearch-dashboards/public';
import { fireEvent, render, waitFor, act } from '@testing-library/react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { BehaviorSubject } from 'rxjs';
import { coreMock } from '../../../../../core/public/mocks';
import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public';
import { createMockedRegisteredUseCases$ } from '../../mocks';

import {
WorkspaceCreator as WorkspaceCreatorComponent,
WorkspaceCreatorProps,
} from './workspace_creator';
import { coreMock } from '../../../../../core/public/mocks';
import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public';
import { WORKSPACE_USE_CASES } from '../../../common/constants';

const workspaceClientCreate = jest
.fn()
Expand Down Expand Up @@ -96,12 +97,7 @@ const WorkspaceCreator = ({
},
},
});
const registeredUseCases$ = new BehaviorSubject([
WORKSPACE_USE_CASES.observability,
WORKSPACE_USE_CASES['security-analytics'],
WORKSPACE_USE_CASES.essentials,
WORKSPACE_USE_CASES.search,
]);
const registeredUseCases$ = createMockedRegisteredUseCases$();

return (
<Provider>
Expand Down Expand Up @@ -139,33 +135,44 @@ describe('WorkspaceCreator', () => {

it('should not create workspace when name is empty', async () => {
const { getByTestId } = render(<WorkspaceCreator />);
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).not.toHaveBeenCalled();
});

it('should not create workspace with invalid name', async () => {
const { getByTestId } = render(<WorkspaceCreator />);
// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});

const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: '~' },
target: {
value: '',
},
});
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).not.toHaveBeenCalled();
});

it('should not create workspace without use cases', async () => {
setHrefSpy.mockReset();
it('should not create workspace with invalid name', async () => {
const { getByTestId } = render(<WorkspaceCreator />);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});

const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
target: { value: '~' },
});
expect(setHrefSpy).not.toHaveBeenCalled();
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).not.toHaveBeenCalled();
});

it('cancel create workspace', async () => {
const { findByText, getByTestId } = render(<WorkspaceCreator />);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
fireEvent.click(getByTestId('workspaceForm-bottomBar-cancelButton'));
await findByText('Discard changes?');
fireEvent.click(getByTestId('confirmModalConfirmButton'));
Expand All @@ -174,6 +181,11 @@ describe('WorkspaceCreator', () => {

it('create workspace with detailed information', async () => {
const { getByTestId } = render(<WorkspaceCreator />);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
Expand Down Expand Up @@ -214,6 +226,11 @@ describe('WorkspaceCreator', () => {
it('should show danger toasts after create workspace failed', async () => {
workspaceClientCreate.mockReturnValueOnce({ result: { id: 'failResult' }, success: false });
const { getByTestId } = render(<WorkspaceCreator />);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
Expand All @@ -232,6 +249,11 @@ describe('WorkspaceCreator', () => {
throw new Error();
});
const { getByTestId } = render(<WorkspaceCreator />);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
Expand All @@ -247,6 +269,11 @@ describe('WorkspaceCreator', () => {

it('create workspace with customized permissions', async () => {
const { getByTestId } = render(<WorkspaceCreator />);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
Expand Down Expand Up @@ -280,16 +307,19 @@ describe('WorkspaceCreator', () => {
const { getByTestId, getByTitle, getByText } = render(
<WorkspaceCreator isDashboardAdmin={true} />
);

// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
});
fireEvent.click(getByTestId('workspaceUseCase-observability'));
fireEvent.click(getByTestId('workspaceForm-select-dataSource-addNew'));
fireEvent.click(getByTestId('workspaceForm-select-dataSource-comboBox'));
await act(() => {
fireEvent.click(getByText('Select'));
});
fireEvent.click(getByText('Select'));
fireEvent.click(getByTitle(dataSourcesList[0].title));

fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import React, { useCallback } from 'react';
import { EuiPage, EuiPageBody, EuiPageContent, euiPaletteColorBlind } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { useObservable } from 'react-use';
import { BehaviorSubject } from 'rxjs';

import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
Expand All @@ -19,13 +18,16 @@ import { DataSource } from '../../../common/types';
import { DataSourceManagementPluginSetup } from '../../../../../plugins/data_source_management/public';
import { WorkspaceUseCase } from '../../types';
import { WorkspaceFormData } from '../workspace_form/types';
import { getUseCaseFeatureConfig } from '../../utils';
import { useFormAvailableUseCases } from '../workspace_form/use_form_available_use_cases';
import { NavigationPublicPluginStart } from '../../../../../plugins/navigation/public';

export interface WorkspaceCreatorProps {
registeredUseCases$: BehaviorSubject<WorkspaceUseCase[]>;
}

export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
const { registeredUseCases$ } = props;
const {
services: {
application,
Expand All @@ -42,13 +44,24 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
navigationUI: NavigationPublicPluginStart['ui'];
}>();

const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled;
const { isOnlyAllowEssential, availableUseCases } = useFormAvailableUseCases({
savedObjects,
registeredUseCases$,
onlyAllowEssentialEnabled: true,
});

const defaultSelectedUseCase = availableUseCases?.[0];
const defaultWorkspaceFormValues: Partial<WorkspaceFormData> = {
color: euiPaletteColorBlind()[0],
...(defaultSelectedUseCase
? {
name: defaultSelectedUseCase.title,
features: [getUseCaseFeatureConfig(defaultSelectedUseCase.id)],
}
: {}),
};

const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled;
const availableUseCases = useObservable(props.registeredUseCases$, []);

const handleWorkspaceFormSubmit = useCallback(
async (data: WorkspaceFormSubmitData) => {
let result;
Expand Down Expand Up @@ -97,6 +110,13 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
[notifications?.toasts, http, application, workspaceClient]
);

const isFormReadyToRender =
application &&
savedObjects &&
// Default values only worked for component mount, should wait for isOnlyAllowEssential and availableUseCases loaded
isOnlyAllowEssential !== undefined &&
availableUseCases !== undefined;

return (
<EuiPage>
<HeaderControl
Expand All @@ -116,7 +136,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
color="subdued"
hasShadow={false}
>
{application && savedObjects && (
{isFormReadyToRender && (
<WorkspaceForm
application={application}
savedObjects={savedObjects}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BehaviorSubject } from 'rxjs';
import { PublicAppInfo, WorkspaceObject } from 'opensearch-dashboards/public';
import { coreMock } from '../../../../../core/public/mocks';
import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public';
import { WORKSPACE_USE_CASES } from '../../../common/constants';
import { createMockedRegisteredUseCases$ } from '../../mocks';
import { WorkspaceDetail } from './workspace_detail';
import { WorkspaceFormProvider, WorkspaceOperationType } from '../workspace_form';
import { MemoryRouter } from 'react-router-dom';
Expand Down Expand Up @@ -122,12 +122,8 @@ const WorkspaceDetailPage = (props: any) => {
},
});

const registeredUseCases$ = new BehaviorSubject([
WORKSPACE_USE_CASES.observability,
WORKSPACE_USE_CASES['security-analytics'],
WORKSPACE_USE_CASES.essentials,
WORKSPACE_USE_CASES.search,
]);
const registeredUseCases$ = createMockedRegisteredUseCases$();

return (
<MemoryRouter>
<WorkspaceFormProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,8 @@ export interface WorkspaceFormProps {
export interface WorkspaceDetailedFormProps extends WorkspaceFormProps {
defaultValues?: WorkspaceFormData;
}

export interface AvailableUseCaseItem
extends Pick<WorkspaceUseCase, 'id' | 'title' | 'features' | 'description' | 'systematic'> {
disabled?: boolean;
}
Loading

0 comments on commit 446aa08

Please sign in to comment.