Skip to content

Commit

Permalink
[Workspace]Add right sidebar to workspace create form (#7750)
Browse files Browse the repository at this point in the history
* Move workspace form inside workspace creator

Signed-off-by: Lin Wang <[email protected]>

* Remove enter details panel

Signed-off-by: Lin Wang <[email protected]>

* Rename workspace_form to workspace_creator_form

Signed-off-by: Lin Wang <[email protected]>

* Clarify workspace form types

Signed-off-by: Lin Wang <[email protected]>

* Add right sidebar to workspace creator

Signed-off-by: Lin Wang <[email protected]>

* Add submitting lock for workspace create page

Signed-off-by: Lin Wang <[email protected]>

* Changeset file for PR #7750 created/updated

* Fix form submitting lock not been covered

Signed-off-by: Lin Wang <[email protected]>

---------

Signed-off-by: Lin Wang <[email protected]>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
(cherry picked from commit 76d7a8b)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent e99e98d commit b4e3fec
Show file tree
Hide file tree
Showing 24 changed files with 952 additions and 282 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/7750.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- [Workspace]Add right sidebar to workspace create form ([#7750](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7750))

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/plugins/workspace/public/components/workspace_creator/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const RIGHT_SIDEBAR_SCROLL_KEY = 'data-right-sidebar-scroll';

export enum RightSidebarScrollField {
Name = 'name',
Description = 'description',
Color = 'color',
UseCase = 'useCase',
DataSource = 'dataSource',
Member = 'member',
}

export const generateRightSidebarScrollProps = (key: RightSidebarScrollField) => {
return { [RIGHT_SIDEBAR_SCROLL_KEY]: key };
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('WorkspaceCreateActionPanel', () => {
formId={formId}
formData={{ name: longName, description: formData.description }}
application={mockApplication}
isSubmitting={false}
/>
);
const createButton = screen.getByText('Create workspace');
Expand All @@ -40,6 +41,7 @@ describe('WorkspaceCreateActionPanel', () => {
formId={formId}
formData={{ name: formData.name, description: longDescription }}
application={mockApplication}
isSubmitting={false}
/>
);
const createButton = screen.getByText('Create workspace');
Expand All @@ -52,9 +54,23 @@ describe('WorkspaceCreateActionPanel', () => {
formId={formId}
formData={formData}
application={mockApplication}
isSubmitting={false}
/>
);
const createButton = screen.getByText('Create workspace');
expect(createButton.closest('button')).not.toBeDisabled();
});

it('should disable the "Create Workspace" and "Cancel" button when submitting', () => {
render(
<WorkspaceCreateActionPanel
formId={formId}
formData={{ name: 'test' }}
application={mockApplication}
isSubmitting
/>
);
expect(screen.getByText('Create workspace').closest('button')).toBeDisabled();
expect(screen.getByText('Cancel').closest('button')).toBeDisabled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiSmallButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiSmallButton, EuiFlexGroup, EuiFlexItem, EuiSmallButtonEmpty } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import React, { useState, useCallback } from 'react';
import type { ApplicationStart } from 'opensearch-dashboards/public';
import type { WorkspaceFormData } from './types';
import { WorkspaceCancelModal } from './workspace_cancel_modal';
import { WorkspaceFormDataState, WorkspaceCancelModal } from '../workspace_form';
import {
MAX_WORKSPACE_DESCRIPTION_LENGTH,
MAX_WORKSPACE_NAME_LENGTH,
} from '../../../common/constants';

interface WorkspaceCreateActionPanelProps {
formId: string;
formData: Partial<Pick<WorkspaceFormData, 'name' | 'description'>>;
formData: Pick<WorkspaceFormDataState, 'name' | 'description'>;
application: ApplicationStart;
isSubmitting: boolean;
}

export const WorkspaceCreateActionPanel = ({
formId,
formData,
application,
isSubmitting,
}: WorkspaceCreateActionPanelProps) => {
const [isCancelModalVisible, setIsCancelModalVisible] = useState(false);
const closeCancelModal = useCallback(() => setIsCancelModalVisible(false), []);
Expand All @@ -34,26 +35,28 @@ export const WorkspaceCreateActionPanel = ({

return (
<>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiSmallButton
<EuiSmallButtonEmpty
data-test-subj="workspaceForm-bottomBar-cancelButton"
onClick={showCancelModal}
disabled={isSubmitting}
>
{i18n.translate('workspace.form.bottomBar.cancel', {
{i18n.translate('workspace.form.right.sidebar.buttons.cancelText', {
defaultMessage: 'Cancel',
})}
</EuiSmallButton>
</EuiSmallButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSmallButton
fill
type="submit"
form={formId}
data-test-subj="workspaceForm-bottomBar-createButton"
disabled={createButtonDisabled}
fill
disabled={createButtonDisabled || isSubmitting}
isLoading={isSubmitting}
>
{i18n.translate('workspace.form.bottomBar.createWorkspace', {
{i18n.translate('workspace.form.right.sidebar.buttons.createWorkspaceText', {
defaultMessage: 'Create workspace',
})}
</EuiSmallButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,29 @@ describe('WorkspaceCreator', () => {
});
expect(notificationToastsAddDanger).not.toHaveBeenCalled();
});

it('should not create workspace API when submitting', async () => {
workspaceClientCreate.mockImplementationOnce(
() =>
new Promise((resolve) => {
setTimeout(resolve, 100);
})
);
const { getByTestId } = render(<WorkspaceCreator />);
// Ensure workspace create form rendered
await waitFor(() => {
expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument();
});
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).toHaveBeenCalledTimes(1);

// Since create button was been disabled, fire form submit event by form directly
fireEvent.submit(getByTestId('workspaceCreatorForm'));
expect(workspaceClientCreate).toHaveBeenCalledTimes(1);

await waitFor(() => {
fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton'));
expect(workspaceClientCreate).toHaveBeenCalledTimes(2);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { EuiPage, EuiPageBody, EuiPageContent, euiPaletteColorBlind } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { BehaviorSubject } from 'rxjs';

import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WorkspaceForm, WorkspaceFormSubmitData, WorkspaceOperationType } from '../workspace_form';
import { WorkspaceFormSubmitData, WorkspaceOperationType } from '../workspace_form';
import { WORKSPACE_DETAIL_APP_ID } from '../../../common/constants';
import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils';
import { WorkspaceClient } from '../../workspace_client';
import { convertPermissionSettingsToPermissions } from '../workspace_form';
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';
import { WorkspaceCreatorForm } from './workspace_creator_form';

export interface WorkspaceCreatorProps {
registeredUseCases$: BehaviorSubject<WorkspaceUseCase[]>;
Expand All @@ -43,6 +43,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
dataSourceManagement?: DataSourceManagementPluginSetup;
navigationUI: NavigationPublicPluginStart['ui'];
}>();
const [isFormSubmitting, setIsFormSubmitting] = useState(false);

const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled;
const { isOnlyAllowEssential, availableUseCases } = useFormAvailableUseCases({
Expand All @@ -52,7 +53,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
});

const defaultSelectedUseCase = availableUseCases?.[0];
const defaultWorkspaceFormValues: Partial<WorkspaceFormData> = {
const defaultWorkspaceFormValues: Partial<WorkspaceFormSubmitData> = {
color: euiPaletteColorBlind()[0],
...(defaultSelectedUseCase
? {
Expand All @@ -65,6 +66,10 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
const handleWorkspaceFormSubmit = useCallback(
async (data: WorkspaceFormSubmitData) => {
let result;
if (isFormSubmitting) {
return;
}
setIsFormSubmitting(true);
try {
const { permissionSettings, selectedDataSources, ...attributes } = data;
const selectedDataSourceIds = (selectedDataSources ?? []).map((ds: DataSource) => {
Expand Down Expand Up @@ -105,9 +110,11 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
text: error instanceof Error ? error.message : JSON.stringify(error),
});
return;
} finally {
setIsFormSubmitting(false);
}
},
[notifications?.toasts, http, application, workspaceClient]
[notifications?.toasts, http, application, workspaceClient, isFormSubmitting]
);

const isFormReadyToRender =
Expand Down Expand Up @@ -137,7 +144,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
hasShadow={false}
>
{isFormReadyToRender && (
<WorkspaceForm
<WorkspaceCreatorForm
application={application}
savedObjects={savedObjects}
onSubmit={handleWorkspaceFormSubmit}
Expand All @@ -146,6 +153,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
dataSourceManagement={dataSourceManagement}
availableUseCases={availableUseCases}
defaultValues={defaultWorkspaceFormValues}
isSubmitting={isFormSubmitting}
/>
)}
</EuiPageContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
$workspaceCreateRightSideBarTopOffset: 116px;
$workspaceCreateRightSideBarBottomOffset: 100px;

.workspaceCreateRightSidebar {
position: sticky;
top: $workspaceCreateRightSideBarTopOffset;
max-height: calc(100vh - $workspaceCreateRightSideBarTopOffset - $workspaceCreateRightSideBarBottomOffset);
overflow: hidden;
display: flex;
flex-direction: column;
width: 280px;

@include ouiBreakpoint("xs","s") {
position: static;
width: 100%;
}
}

.workspaceCreateRightSideBarContentWrapper {
overflow-y: scroll;

@include ouiBreakpoint("xs","s") {
overflow: visible;
}
}

.workspaceCreateRightSideBarActionsWrapper {
padding: $ouiSizeM;
border-radius: $ouiSizeM;
background: $ouiColorEmptyShade;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { fireEvent, render } from '@testing-library/react';
import { coreMock } from '../../../../../core/public/mocks';
import { DataSourceManagementPluginSetup } from '../../../../../plugins/data_source_management/public';
import { createMockedRegisteredUseCases } from '../../mocks';
import { WorkspaceOperationType } from './constants';
import { WorkspaceForm } from './workspace_form';
import { WorkspaceOperationType } from '../workspace_form';
import { WorkspaceCreatorForm } from './workspace_creator_form';

const mockCoreStart = coreMock.createStart();

Expand Down Expand Up @@ -37,7 +37,8 @@ const setup = (
};

return render(
<WorkspaceForm
<WorkspaceCreatorForm
isSubmitting={false}
application={application}
savedObjects={savedObjects}
operationType={WorkspaceOperationType.Create}
Expand All @@ -53,13 +54,13 @@ describe('WorkspaceForm', () => {
it('should enable data source panel for dashboard admin and when data source is enabled', () => {
const { getByText } = setup(true, mockDataSourceManagementSetup);

expect(getByText('Associate data source')).toBeInTheDocument();
expect(getByText('Associate data sources')).toBeInTheDocument();
});

it('should not display data source panel for non dashboard admin', () => {
const { queryByText } = setup(false, mockDataSourceManagementSetup);

expect(queryByText('Associate data source')).not.toBeInTheDocument();
expect(queryByText('Associate data sources')).not.toBeInTheDocument();
});

it('should not display data source panel when data source is disabled', () => {
Expand Down
Loading

0 comments on commit b4e3fec

Please sign in to comment.