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

[Workspaces]Add features in use case card and preselect first use case #7703

Merged
Merged
Show file tree
Hide file tree
Changes from 6 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/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 @@ -116,18 +129,22 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
color="subdued"
hasShadow={false}
>
{application && savedObjects && (
<WorkspaceForm
application={application}
savedObjects={savedObjects}
onSubmit={handleWorkspaceFormSubmit}
operationType={WorkspaceOperationType.Create}
permissionEnabled={isPermissionEnabled}
dataSourceManagement={dataSourceManagement}
availableUseCases={availableUseCases}
defaultValues={defaultWorkspaceFormValues}
/>
)}
{application &&
savedObjects &&
// Default values only worked for component mount, should wait for isOnlyAllowEssential and availableUseCases loaded
isOnlyAllowEssential !== undefined &&
availableUseCases !== undefined && (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe assign this to a readable name?

Suggested change
{application &&
savedObjects &&
// Default values only worked for component mount, should wait for isOnlyAllowEssential and availableUseCases loaded
isOnlyAllowEssential !== undefined &&
availableUseCases !== undefined && (
{application &&
savedObjects &&
// Default values only worked for component mount, should wait for isOnlyAllowEssential and availableUseCases loaded
isOnlyAllowEssential !== undefined &&
availableUseCases !== undefined && (

<WorkspaceForm
application={application}
savedObjects={savedObjects}
onSubmit={handleWorkspaceFormSubmit}
operationType={WorkspaceOperationType.Create}
permissionEnabled={isPermissionEnabled}
dataSourceManagement={dataSourceManagement}
availableUseCases={availableUseCases}
defaultValues={defaultWorkspaceFormValues}
/>
)}
</EuiPageContent>
</EuiPageBody>
</EuiPage>
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
Loading