Skip to content

Commit

Permalink
feat: customize management workspace (opensearch-project#122)
Browse files Browse the repository at this point in the history
* hide workspace feature for management workspace settings

Signed-off-by: yuye-aws <[email protected]>

* add workspace settings to management workspace

Signed-off-by: yuye-aws <[email protected]>

* prohibit name change for public and management workspace

Signed-off-by: yuye-aws <[email protected]>

* fix empty permission bug

Signed-off-by: yuye-aws <[email protected]>

* cancel export

Signed-off-by: yuye-aws <[email protected]>

* position fix

Signed-off-by: yuye-aws <[email protected]>

* make name text field readonly

Signed-off-by: yuye-aws <[email protected]>

* refactor render logic

Signed-off-by: yuye-aws <[email protected]>

---------

Signed-off-by: yuye-aws <[email protected]>
  • Loading branch information
yuye-aws authored and ruanyl committed Sep 15, 2023
1 parent 43c144f commit cb3e7c1
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 83 deletions.
6 changes: 6 additions & 0 deletions src/plugins/workspace/public/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { WorkspacePermissionSetting } from './workspace_creator';
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*/

export { WorkspaceCreator } from './workspace_creator';
export { WorkspacePermissionSetting } from './workspace_permission_setting_panel';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, { useCallback } from 'react';
import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageContent } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WorkspaceForm, WorkspaceFormData } from './workspace_form';
import { WorkspaceForm, WorkspaceFormSubmitData } from './workspace_form';
import { WORKSPACE_OVERVIEW_APP_ID, WORKSPACE_OP_TYPE_CREATE } from '../../../common/constants';
import { formatUrlWithWorkspaceId } from '../../utils';
import { WorkspaceClient } from '../../workspace_client';
Expand All @@ -18,7 +18,7 @@ export const WorkspaceCreator = () => {
} = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>();

const handleWorkspaceFormSubmit = useCallback(
async (data: WorkspaceFormData) => {
async (data: WorkspaceFormSubmitData) => {
let result;
try {
result = await workspaceClient.create(data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
AppNavLinkStatus,
ApplicationStart,
DEFAULT_APP_CATEGORIES,
MANAGEMENT_WORKSPACE_ID,
PUBLIC_WORKSPACE_ID,
} from '../../../../../core/public';
import { useApplications } from '../../hooks';
import {
Expand Down Expand Up @@ -63,16 +65,20 @@ interface WorkspaceFeatureGroup {
features: WorkspaceFeature[];
}

export interface WorkspaceFormData {
export interface WorkspaceFormSubmitData {
name: string;
description?: string;
features: string[];
features?: string[];
color?: string;
icon?: string;
defaultVISTheme?: string;
permissions: WorkspacePermissionSetting[];
}

export interface WorkspaceFormData extends WorkspaceFormSubmitData {
id: string;
}

type WorkspaceFormErrors = Omit<{ [key in keyof WorkspaceFormData]?: string }, 'permissions'> & {
permissions?: string[];
};
Expand Down Expand Up @@ -103,7 +109,7 @@ const defaultVISThemeOptions = [{ label: 'Categorical', value: 'categorical' }];

interface WorkspaceFormProps {
application: ApplicationStart;
onSubmit?: (formData: WorkspaceFormData) => void;
onSubmit?: (formData: WorkspaceFormSubmitData) => void;
defaultValues?: WorkspaceFormData;
opType?: string;
}
Expand All @@ -114,6 +120,10 @@ export const WorkspaceForm = ({
defaultValues,
opType,
}: WorkspaceFormProps) => {
const workspaceId = defaultValues?.id;
const workspaceNameReadOnly =
workspaceId === PUBLIC_WORKSPACE_ID || workspaceId === MANAGEMENT_WORKSPACE_ID;
const isShowingWorkspaceFeatures = workspaceId !== MANAGEMENT_WORKSPACE_ID;
const applications = useApplications(application);

const [name, setName] = useState(defaultValues?.name);
Expand Down Expand Up @@ -311,7 +321,7 @@ export const WorkspaceForm = ({
permissionErrors[i] = i18n.translate('workspace.form.permission.invalidate.group', {
defaultMessage: 'Invalid user group',
});
continue;
continue; // this line is need for more conditions
}
}
if (permissionErrors.some((error) => !!error)) {
Expand Down Expand Up @@ -355,7 +365,11 @@ export const WorkspaceForm = ({
</EuiTitle>
<EuiSpacer />
<EuiFormRow label="Name" isInvalid={!!formErrors.name} error={formErrors.name}>
<EuiFieldText value={name} onChange={handleNameInputChange} />
<EuiFieldText
value={name}
onChange={handleNameInputChange}
readOnly={workspaceNameReadOnly}
/>
</EuiFormRow>
<EuiFormRow
label={
Expand Down Expand Up @@ -387,69 +401,73 @@ export const WorkspaceForm = ({
</EuiFormRow>
</EuiPanel>
<EuiSpacer />
<EuiPanel>
<EuiTitle size="s">
<h2>Workspace features</h2>
</EuiTitle>
<EuiFlexGrid style={{ paddingLeft: 20, paddingTop: 20 }} columns={2}>
{featureOrGroups.map((featureOrGroup) => {
const features = isWorkspaceFeatureGroup(featureOrGroup) ? featureOrGroup.features : [];
const selectedIds = selectedFeatureIds.filter((id) =>
(isWorkspaceFeatureGroup(featureOrGroup)
{isShowingWorkspaceFeatures && (
<EuiPanel>
<EuiTitle size="s">
<h2>Workspace features</h2>
</EuiTitle>
<EuiFlexGrid style={{ paddingLeft: 20, paddingTop: 20 }} columns={2}>
{featureOrGroups.map((featureOrGroup) => {
const features = isWorkspaceFeatureGroup(featureOrGroup)
? featureOrGroup.features
: [featureOrGroup]
).find((item) => item.id === id)
);
return (
<EuiFlexItem key={featureOrGroup.name}>
<EuiCheckbox
id={
isWorkspaceFeatureGroup(featureOrGroup)
? featureOrGroup.name
: featureOrGroup.id
}
onChange={
isWorkspaceFeatureGroup(featureOrGroup)
? handleFeatureGroupChange
: handleFeatureCheckboxChange
}
label={`${featureOrGroup.name}${
features.length > 0 ? `(${selectedIds.length}/${features.length})` : ''
}`}
checked={selectedIds.length > 0}
disabled={
!isWorkspaceFeatureGroup(featureOrGroup) &&
isDefaultCheckedFeatureId(featureOrGroup.id)
}
indeterminate={
isWorkspaceFeatureGroup(featureOrGroup) &&
selectedIds.length > 0 &&
selectedIds.length < features.length
}
/>
{isWorkspaceFeatureGroup(featureOrGroup) && (
<EuiCheckboxGroup
options={featureOrGroup.features.map((item) => ({
id: item.id,
label: item.name,
disabled: isDefaultCheckedFeatureId(item.id),
}))}
idToSelectedMap={selectedIds.reduce(
(previousValue, currentValue) => ({
...previousValue,
[currentValue]: true,
}),
{}
)}
onChange={handleFeatureChange}
style={{ marginLeft: 40 }}
: [];
const selectedIds = selectedFeatureIds.filter((id) =>
(isWorkspaceFeatureGroup(featureOrGroup)
? featureOrGroup.features
: [featureOrGroup]
).find((item) => item.id === id)
);
return (
<EuiFlexItem key={featureOrGroup.name}>
<EuiCheckbox
id={
isWorkspaceFeatureGroup(featureOrGroup)
? featureOrGroup.name
: featureOrGroup.id
}
onChange={
isWorkspaceFeatureGroup(featureOrGroup)
? handleFeatureGroupChange
: handleFeatureCheckboxChange
}
label={`${featureOrGroup.name}${
features.length > 0 ? `(${selectedIds.length}/${features.length})` : ''
}`}
checked={selectedIds.length > 0}
disabled={
!isWorkspaceFeatureGroup(featureOrGroup) &&
isDefaultCheckedFeatureId(featureOrGroup.id)
}
indeterminate={
isWorkspaceFeatureGroup(featureOrGroup) &&
selectedIds.length > 0 &&
selectedIds.length < features.length
}
/>
)}
</EuiFlexItem>
);
})}
</EuiFlexGrid>
</EuiPanel>
{isWorkspaceFeatureGroup(featureOrGroup) && (
<EuiCheckboxGroup
options={featureOrGroup.features.map((item) => ({
id: item.id,
label: item.name,
disabled: isDefaultCheckedFeatureId(item.id),
}))}
idToSelectedMap={selectedIds.reduce(
(previousValue, currentValue) => ({
...previousValue,
[currentValue]: true,
}),
{}
)}
onChange={handleFeatureChange}
style={{ marginLeft: 40 }}
/>
)}
</EuiFlexItem>
);
})}
</EuiFlexGrid>
</EuiPanel>
)}
<EuiSpacer />
<EuiPanel>
<EuiTitle size="s">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,47 @@ import { i18n } from '@osd/i18n';
import { of } from 'rxjs';
import { WorkspaceAttribute } from 'opensearch-dashboards/public';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WorkspaceForm, WorkspaceFormData } from '../workspace_creator/workspace_form';
import {
WorkspaceForm,
WorkspaceFormSubmitData,
WorkspaceFormData,
} from '../workspace_creator/workspace_form';
import { WORKSPACE_OVERVIEW_APP_ID, WORKSPACE_OP_TYPE_UPDATE } from '../../../common/constants';
import { DeleteWorkspaceModal } from '../delete_workspace_modal';
import { formatUrlWithWorkspaceId } from '../../utils';
import { WorkspaceClient } from '../../workspace_client';
import { WorkspacePermissionSetting } from '../';

interface WorkspaceWithPermission extends WorkspaceAttribute {
permissions?: WorkspacePermissionSetting[];
}

function getFormDataFromWorkspace(
currentWorkspace: WorkspaceAttribute | null | undefined
): WorkspaceFormData {
const currentWorkspaceWithPermission = (currentWorkspace || {}) as WorkspaceWithPermission;
return {
...currentWorkspaceWithPermission,
permissions: currentWorkspaceWithPermission.permissions || [],
};
}
export const WorkspaceUpdater = () => {
const {
services: { application, workspaces, notifications, http, workspaceClient },
} = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>();

const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null));

const excludedAttribute = 'id';
const { [excludedAttribute]: removedProperty, ...otherAttributes } =
currentWorkspace || ({} as WorkspaceAttribute);

const [deleteWorkspaceModalVisible, setDeleteWorkspaceModalVisible] = useState(false);
const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState<
Omit<WorkspaceAttribute, 'id'>
>(otherAttributes);
const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState<WorkspaceFormData>(
getFormDataFromWorkspace(currentWorkspace)
);

useEffect(() => {
const { id, ...others } = currentWorkspace || ({} as WorkspaceAttribute);
setCurrentWorkspaceFormData(others);
}, [workspaces, currentWorkspace, excludedAttribute]);
setCurrentWorkspaceFormData(getFormDataFromWorkspace(currentWorkspace));
}, [currentWorkspace]);

const handleWorkspaceFormSubmit = useCallback(
async (data: WorkspaceFormData) => {
async (data: WorkspaceFormSubmitData) => {
let result;
if (!currentWorkspace) {
notifications?.toasts.addDanger({
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { IWorkspaceDBImpl } from './types';
import { WorkspaceClientWithSavedObject } from './workspace_client';
import { WorkspaceSavedObjectsClientWrapper } from './saved_objects';
import { registerRoutes } from './routes';
import { WORKSPACE_OVERVIEW_APP_ID } from '../common/constants';
import { WORKSPACE_OVERVIEW_APP_ID, WORKSPACE_UPDATE_APP_ID } from '../common/constants';
import { ConfigSchema, FEATURE_FLAG_KEY_IN_UI_SETTING } from '../config';

export class WorkspacePlugin implements Plugin<{}, {}> {
Expand Down Expand Up @@ -177,7 +177,11 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
name: i18n.translate('workspaces.management.workspace.default.name', {
defaultMessage: 'Management',
}),
features: [`@${DEFAULT_APP_CATEGORIES.management.id}`, WORKSPACE_OVERVIEW_APP_ID],
features: [
`@${DEFAULT_APP_CATEGORIES.management.id}`,
WORKSPACE_OVERVIEW_APP_ID,
WORKSPACE_UPDATE_APP_ID,
],
},
managementWorkspaceACL.getPermissions()
),
Expand Down

0 comments on commit cb3e7c1

Please sign in to comment.