Skip to content

Commit

Permalink
refactor dupicate_model code and fix test error
Browse files Browse the repository at this point in the history
  • Loading branch information
yubonluo committed Mar 22, 2024
1 parent 2a00d66 commit f7c18b4
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 1,438 deletions.
1,317 changes: 42 additions & 1,275 deletions src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/core/server/saved_objects/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export function registerRoutes({
registerLogLegacyImportRoute(router, logger);
registerExportRoute(router, config);
registerImportRoute(router, config);
registerCopyRoute(router, config);
registerResolveImportErrorsRoute(router, config);
registerCopyRoute(router, config);

const internalRouter = http.createRouter('/internal/saved_objects/');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe(`POST ${URL}`, () => {
const result = await supertest(httpSetup.server.listener).post(URL).send({}).expect(400);

expect(result.body.message).toMatchInlineSnapshot(
`"[request body.objects]: expected value of type [array] but got [undefined]"`
`"[request body.targetWorkspace]: expected value of type [string] but got [undefined]"`
);
});

Expand Down Expand Up @@ -157,7 +157,7 @@ describe(`POST ${URL}`, () => {
.expect(400);

expect(result.body.message).toMatchInlineSnapshot(
`"Trying to copy object(s) with unsupported types: unknown:my-pattern"`
`"Trying to export object(s) with non-exportable types: unknown:my-pattern"`
);
});

Expand Down

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

Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Any modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import React from 'react';
Expand All @@ -24,24 +18,23 @@ import {
EuiComboBox,
EuiFormRow,
EuiCheckbox,
EuiComboBoxOptionOption,
EuiInMemoryTable,
EuiToolTip,
EuiIcon,
EuiCallOut,
EuiText,
EuiTextColor,
} from '@elastic/eui';
import {
HttpSetup,
NotificationsStart,
WorkspaceAttribute,
WorkspacesStart,
} from 'opensearch-dashboards/public';
import { HttpSetup, NotificationsStart, WorkspacesStart } from 'opensearch-dashboards/public';
import { i18n } from '@osd/i18n';
import { SavedObjectWithMetadata } from '../../../../common';
import { getSavedObjectLabel } from '../../../../public';

type WorkspaceOption = EuiComboBoxOptionOption<WorkspaceAttribute>;
import {
WorkspaceOption,
capitalizeFirstLetter,
getTargetWorkspacesOptions,
workspaceToOption,
} from './utils';

export enum DuplicateMode {
Selected = 'selected',
Expand All @@ -67,95 +60,51 @@ interface Props extends ShowDuplicateModalProps {
interface State {
allSelectedObjects: SavedObjectWithMetadata[];
workspaceOptions: WorkspaceOption[];
allWorkspaceOptions: WorkspaceOption[];
targetWorkspaceOption: WorkspaceOption[];
isLoading: boolean;
isIncludeReferencesDeepChecked: boolean;
savedObjectTypeInfoMap: Map<string, [number, boolean]>;
}

function capitalizeFirstLetter(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}

export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
private isMounted = false;

constructor(props: Props) {
super(props);

this.state = {
allSelectedObjects: this.props.selectedSavedObjects,
workspaceOptions: [],
allWorkspaceOptions: [],
targetWorkspaceOption: [],
isLoading: false,
isIncludeReferencesDeepChecked: true,
savedObjectTypeInfoMap: new Map<string, [number, boolean]>(),
};
}

workspaceToOption = (
workspace: WorkspaceAttribute,
currentWorkspaceName?: string
): WorkspaceOption => {
// add (current) after current workspace name
let workspaceName = workspace.name;
if (workspace.name === currentWorkspaceName) {
workspaceName += ' (current)';
}
return {
label: workspaceName,
key: workspace.id,
value: workspace,
};
};

async componentDidMount() {
const { workspaces } = this.props;
const { workspaces, duplicateMode } = props;
const currentWorkspace = workspaces.currentWorkspace$.value;
const currentWorkspaceName = currentWorkspace?.name;
const targetWorkspaces = this.getTargetWorkspaces();

// current workspace is the first option
const workspaceOptions = [
...(currentWorkspace ? [this.workspaceToOption(currentWorkspace, currentWorkspaceName)] : []),
...targetWorkspaces
.filter((workspace: WorkspaceAttribute) => workspace.name !== currentWorkspaceName)
.map((workspace: WorkspaceAttribute) =>
this.workspaceToOption(workspace, currentWorkspaceName)
),
];
const currentWorkspaceId = currentWorkspace?.id;
const targetWorkspacesOptions = getTargetWorkspacesOptions(workspaces, currentWorkspaceId);

this.setState({
workspaceOptions,
allWorkspaceOptions: workspaceOptions,
});

const { duplicateMode } = this.props;
// If user click 'Duplicate All' button, saved objects will be categoried by type.
const savedObjectTypeInfoMap = new Map<string, [number, boolean]>();
if (duplicateMode === DuplicateMode.All) {
const { allSelectedObjects } = this.state;
const categorizedObjects = groupBy(allSelectedObjects, (object) => object.type);
const savedObjectTypeInfoMap = new Map<string, [number, boolean]>();
const categorizedObjects = groupBy(props.selectedSavedObjects, (object) => object.type);
for (const [savedObjectType, savedObjects] of Object.entries(categorizedObjects)) {
savedObjectTypeInfoMap.set(savedObjectType, [savedObjects.length, true]);
}
this.setState({ savedObjectTypeInfoMap });
}

this.state = {
allSelectedObjects: props.selectedSavedObjects,
// current workspace is the first option
workspaceOptions: [
...(currentWorkspace ? [workspaceToOption(currentWorkspace, currentWorkspaceId)] : []),
...targetWorkspacesOptions,
],
targetWorkspaceOption: [],
isLoading: false,
isIncludeReferencesDeepChecked: true,
savedObjectTypeInfoMap,
};
this.isMounted = true;
}

componentWillUnmount() {
this.isMounted = false;
}

getTargetWorkspaces = () => {
const { workspaces } = this.props;
const workspaceList = workspaces.workspaceList$.value;
return workspaceList.filter((workspace) => !workspace.libraryReadonly);
};

duplicateSavedObjects = async (savedObjects: SavedObjectWithMetadata[]) => {
this.setState({
isLoading: true,
Expand All @@ -176,14 +125,6 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
}
};

onSearchWorkspaceChange = (searchValue: string) => {
this.setState({
workspaceOptions: this.state.allWorkspaceOptions.filter((item) =>
item.label.includes(searchValue)
),
});
};

onTargetWorkspaceChange = (targetWorkspaceOption: WorkspaceOption[]) => {
this.setState({
targetWorkspaceOption,
Expand All @@ -196,6 +137,7 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
}));
};

// Choose whether to copy a certain type or not.
changeIncludeSavedObjectType = (savedObjectType: string) => {
const { savedObjectTypeInfoMap } = this.state;
const savedObjectTypeInfo = savedObjectTypeInfoMap.get(savedObjectType);
Expand All @@ -206,6 +148,7 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
}
};

// A checkbox showing the type and count of save objects.
renderDuplicateObjectCategory = (
savedObjectType: string,
savedObjectTypeCount: number,
Expand Down Expand Up @@ -267,6 +210,8 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
if (duplicateMode === DuplicateMode.All) {
selectedObjects = selectedObjects.filter((item) => this.isSavedObjectTypeIncluded(item.type));
}
// If the target workspace is selected, all saved objects will be retained.
// If the target workspace has been selected, filter out the saved objects that belongs to the workspace.
const includedSelectedObjects = selectedObjects.filter((item) =>
!!targetWorkspaceId && !!item.workspaces ? !item.workspaces.includes(targetWorkspaceId) : true
);
Expand All @@ -279,19 +224,21 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
}

const warningMessageForOnlyOneSavedObject = (
<p>
<b style={{ color: '#000' }}>1</b> saved object will <b style={{ color: '#000' }}>not</b> be
copied, because it has already existed in the selected workspace or it is worksapce itself.
</p>
<EuiText>
<EuiTextColor color="danger">1</EuiTextColor> saved object will{' '}
<EuiTextColor color="danger">not</EuiTextColor> be copied, because it has already existed in
the selected workspace or it is worksapce itself.
</EuiText>
);
const warningMessageForMultipleSavedObjects = (
<p>
<b style={{ color: '#000' }}>{ignoredSelectedObjectsLength}</b> saved objects will{' '}
<b style={{ color: '#000' }}>not</b> be copied, because they have already existed in the
selected workspace or they are workspaces themselves.
</p>
<EuiText>
<EuiTextColor color="danger">{ignoredSelectedObjectsLength}</EuiTextColor> saved objects
will <EuiTextColor color="danger">not</EuiTextColor> be copied, because they have already
existed in selected workspace or they are workspaces themselves.
</EuiText>
);

// Show the number and reason why some saved objects cannot be duplicated.
const ignoreSomeObjectsChildren: React.ReactChild = (
<>
<EuiCallOut
Expand Down Expand Up @@ -331,12 +278,10 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
<EuiModalBody>
<EuiFormRow
fullWidth
label={
<FormattedMessage
id="savedObjectsManagement.objectsTable.duplicateModal.targetWorkspacelabel"
defaultMessage="Destination workspace"
/>
}
label={i18n.translate(
'savedObjectsManagement.objectsTable.duplicateModal.targetWorkspacelabel',
{ defaultMessage: 'Destination workspace' }
)}
>
<>
<EuiText size="s" color="subdued">
Expand All @@ -348,7 +293,6 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
onChange={this.onTargetWorkspaceChange}
selectedOptions={targetWorkspaceOption}
singleSelection={{ asPlainText: true }}
onSearchChange={this.onSearchWorkspaceChange}
isClearable={false}
isInvalid={!confirmDuplicateButtonEnabled}
/>
Expand All @@ -361,12 +305,10 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {

<EuiFormRow
fullWidth
label={
<FormattedMessage
id="savedObjectsManagement.objectsTable.duplicateModal.relatedObjects"
defaultMessage="Related Objects"
/>
}
label={i18n.translate(
'savedObjectsManagement.objectsTable.duplicateModal.relatedObjects',
{ defaultMessage: 'Related Objects' }
)}
>
<>
<EuiText size="s" color="subdued">
Expand All @@ -377,12 +319,10 @@ export class SavedObjectsDuplicateModal extends React.Component<Props, State> {
<EuiSpacer size="s" />
<EuiCheckbox
id={'includeReferencesDeep'}
label={
<FormattedMessage
id="savedObjectsManagement.objectsTable.duplicateModal.includeReferencesDeepLabel"
defaultMessage="Duplicate related objects"
/>
}
label={i18n.translate(
'savedObjectsManagement.objectsTable.duplicateModal.includeReferencesDeepLabel',
{ defaultMessage: 'Duplicate related objects' }
)}
checked={isIncludeReferencesDeepChecked}
onChange={this.changeIncludeReferencesDeep}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,11 @@ describe('Header', () => {
onExportAll: () => {},
onImport: () => {},
onRefresh: () => {},
onCopy: () => {},
onDuplicate: () => {},
title: 'Saved Objects',
selectedCount: 0,
objectCount: 4,
filteredCount: 2,
showDuplicateAll: false,
hideImport: false,
};

const component = shallow(<Header {...props} />);
Expand All @@ -60,38 +57,15 @@ describe('Header - workspace enabled', () => {
onExportAll: () => {},
onImport: () => {},
onRefresh: () => {},
onCopy: () => {},
onDuplicate: () => {},
title: 'Saved Objects',
selectedCount: 0,
objectCount: 4,
filteredCount: 2,
showDuplicateAll: true,
hideImport: false,
};

const component = shallow(<Header {...props} />);

expect(component.find('EuiButtonEmpty[data-test-subj="duplicateObjects"]').exists()).toBe(true);
});

it('should hide `Import` button for application home state', () => {
const props = {
onExportAll: () => {},
onImport: () => {},
onRefresh: () => {},
onCopy: () => {},
onDuplicate: () => {},
title: 'Saved Objects',
selectedCount: 0,
objectCount: 4,
filteredCount: 2,
showDuplicateAll: true,
hideImport: true,
};

const component = shallow(<Header {...props} />);

expect(component.find('EuiButtonEmpty[data-test-subj="importObjects"]').exists()).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export class Table extends PureComponent<TableProps, TableState> {
{
name: i18n.translate(
'savedObjectsManagement.objectsTable.table.columnActions.viewRelationshipsActionName',
{ defaultMessage: 'View object relationships' }
{ defaultMessage: 'Relationships' }
),
description: i18n.translate(
'savedObjectsManagement.objectsTable.table.columnActions.viewRelationshipsActionDescription',
Expand Down
Loading

0 comments on commit f7c18b4

Please sign in to comment.