Skip to content

Commit

Permalink
feat: duplicate selected objects (opensearch-project#113)
Browse files Browse the repository at this point in the history
* fix typo

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* adjust copy modal

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* list workspace with write permission on copy modal

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* add copy icon and move getcopyworkspaces function from copy_modal to saved_object table

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* fix duplicate error in public workspace and change copy to duplicate all in header

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* bug fix: create saved objects in public workspace

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* update snapshots

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* remove unused import

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* change validate schema

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* behavior subject bug fix for workspace plugin

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

---------

Signed-off-by: yuye-aws <yuyezhu@amazon.com>
# Conflicts:
#	src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
#	src/core/server/saved_objects/permission_control/acl.test.ts
#	src/core/server/saved_objects/permission_control/client.ts
#	src/plugins/saved_objects_management/public/management_section/objects_table/components/copy_modal.tsx
#	src/plugins/workspace/server/plugin.ts
#	src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts
  • Loading branch information
yuye-aws authored and yubonluo committed Mar 21, 2024
1 parent 783863b commit 5e472e1
Show file tree
Hide file tree
Showing 13 changed files with 1,560 additions and 215 deletions.
1,535 changes: 1,384 additions & 151 deletions src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 { HttpStart } from 'src/core/public';
import { WorkspacePermissionMode } from '../../../../core/public';

export async function getWorkspacesWithWritePermission(http: HttpStart) {
return await http.post('/api/workspaces/_list', {
body: JSON.stringify({
permissionModes: [WorkspacePermissionMode.Management, WorkspacePermissionMode.LibraryWrite],
}),
});
}
1 change: 1 addition & 0 deletions src/plugins/saved_objects_management/public/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ export { createFieldList } from './create_field_list';
export { getAllowedTypes } from './get_allowed_types';
export { filterQuery } from './filter_query';
export { copySavedObjects } from './copy_saved_objects';
export { getWorkspacesWithWritePermission } from './get_workspaces_with_write_permission';

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

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

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
Expand Up @@ -23,19 +23,18 @@ import {
EuiSpacer,
EuiComboBox,
EuiFormRow,
EuiSwitch,
EuiCheckbox,
EuiComboBoxOptionOption,
EuiInMemoryTable,
EuiToolTip,
EuiIcon,
EuiCallOut,
EuiText,
} from '@elastic/eui';
import { WorkspaceAttribute, WorkspacesStart } from 'opensearch-dashboards/public';
import { i18n } from '@osd/i18n';
import { iteratorSymbol } from 'immer/dist/internal';
import { SavedObjectWithMetadata } from '../../../types';
import { getSavedObjectLabel } from '../../../lib';
import { SAVED_OBJECT_TYPE_WORKSAPCE } from '../../../constants';

type WorkspaceOption = EuiComboBoxOptionOption<WorkspaceAttribute>;

Expand All @@ -47,11 +46,12 @@ interface Props {
targetWorkspace: string
) => Promise<void>;
onClose: () => void;
seletedSavedObjects: SavedObjectWithMetadata[];
getCopyWorkspaces: () => Promise<WorkspaceAttribute[]>;
selectedSavedObjects: SavedObjectWithMetadata[];
}

interface State {
allSeletedObjects: SavedObjectWithMetadata[];
allSelectedObjects: SavedObjectWithMetadata[];
workspaceOptions: WorkspaceOption[];
allWorkspaceOptions: WorkspaceOption[];
targetWorkspaceOption: WorkspaceOption[];
Expand All @@ -66,7 +66,7 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
super(props);

this.state = {
allSeletedObjects: this.props.seletedSavedObjects,
allSelectedObjects: this.props.selectedSavedObjects,
workspaceOptions: [],
allWorkspaceOptions: [],
targetWorkspaceOption: [],
Expand All @@ -80,21 +80,21 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
};

async componentDidMount() {
const { workspaces } = this.props;
const workspaceList = await workspaces.client.workspaceList$;
const currentWorkspace = await workspaces.client.currentWorkspace$;
const { workspaces, getCopyWorkspaces } = this.props;
const workspaceList = await getCopyWorkspaces();
const currentWorkspace = workspaces.currentWorkspace$;

if (!!currentWorkspace?.value?.name) {
const currentWorkspaceName = currentWorkspace.value.name;
const filteredWorkspaceOptions = workspaceList.value
const filteredWorkspaceOptions = workspaceList
.map(this.workspaceToOption)
.filter((item) => item.label !== currentWorkspaceName);
.filter((item: WorkspaceOption) => item.label !== currentWorkspaceName);
this.setState({
workspaceOptions: filteredWorkspaceOptions,
allWorkspaceOptions: filteredWorkspaceOptions,
});
} else {
const allWorkspaceOptions = workspaceList.value.map(this.workspaceToOption);
const allWorkspaceOptions = workspaceList.map(this.workspaceToOption);
this.setState({
workspaceOptions: allWorkspaceOptions,
allWorkspaceOptions,
Expand Down Expand Up @@ -153,18 +153,16 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
workspaceOptions,
targetWorkspaceOption,
isIncludeReferencesDeepChecked,
allSeletedObjects,
allSelectedObjects,
} = this.state;
const targetWorkspaceId = targetWorkspaceOption?.at(0)?.key;
const includedSeletedObjects = allSeletedObjects.filter((item) =>
!!targetWorkspaceId && !!item.workspaces
? !item.workspaces.includes(targetWorkspaceId)
: true && item.type !== SAVED_OBJECT_TYPE_WORKSAPCE
const includedSelectedObjects = allSelectedObjects.filter((item) =>
!!targetWorkspaceId && !!item.workspaces ? !item.workspaces.includes(targetWorkspaceId) : true
);
const ignoredSeletedObjectsLength = allSeletedObjects.length - includedSeletedObjects.length;
const ignoredSelectedObjectsLength = allSelectedObjects.length - includedSelectedObjects.length;

let confirmCopyButtonEnabled = false;
if (!!targetWorkspaceId && includedSeletedObjects.length > 0) {
if (!!targetWorkspaceId && includedSelectedObjects.length > 0) {
confirmCopyButtonEnabled = true;
}

Expand All @@ -176,7 +174,7 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
);
const warningMessageForMultipleSavedObjects = (
<p>
<b style={{ color: '#000' }}>{ignoredSeletedObjectsLength}</b> saved objects will{' '}
<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 worksapces themselves.
</p>
Expand All @@ -188,9 +186,9 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
title="Some saved objects will be ignored."
color="warning"
iconType="help"
aria-disabled={ignoredSeletedObjectsLength === 0}
aria-disabled={ignoredSelectedObjectsLength === 0}
>
{ignoredSeletedObjectsLength === 1
{ignoredSelectedObjectsLength === 1
? warningMessageForOnlyOneSavedObject
: warningMessageForMultipleSavedObjects}
</EuiCallOut>
Expand All @@ -208,7 +206,11 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
<EuiModalHeaderTitle>
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.title"
defaultMessage="Copy saved objects"
defaultMessage={
'Duplicate ' +
allSelectedObjects.length.toString() +
(allSelectedObjects.length > 1 ? ' objects?' : ' object?')
}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
Expand All @@ -219,36 +221,61 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
label={
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.targetWorkspacelabel"
defaultMessage="Select a workspace to copy to"
defaultMessage="Destination workspace"
/>
}
>
<EuiComboBox
options={workspaceOptions}
onChange={this.onTargetWorkspaceChange}
selectedOptions={targetWorkspaceOption}
singleSelection={{ asPlainText: true }}
onSearchChange={this.onSearchWorkspaceChange}
isClearable={false}
isInvalid={!confirmCopyButtonEnabled}
/>
<>
<EuiText size="s" color="subdued">
{'Specify a workspace where the objects will be duplicated.'}
</EuiText>
<EuiSpacer size="s" />
<EuiComboBox
options={workspaceOptions}
onChange={this.onTargetWorkspaceChange}
selectedOptions={targetWorkspaceOption}
singleSelection={{ asPlainText: true }}
onSearchChange={this.onSearchWorkspaceChange}
isClearable={false}
isInvalid={!confirmCopyButtonEnabled}
/>
</>
</EuiFormRow>

<EuiSpacer size="m" />
<EuiSwitch
name="includeReferencesDeep"

<EuiFormRow
fullWidth
label={
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.includeReferencesDeepLabel"
defaultMessage="Include related objects"
id="savedObjectsManagement.objectsTable.copyModal.relatedObjects"
defaultMessage="Related Objects"
/>
}
checked={isIncludeReferencesDeepChecked}
onChange={this.changeIncludeReferencesDeep}
/>
>
<>
<EuiText size="s" color="subdued">
{
'We recommended duplicating related objects to ensure your duplicated objects will continue to function.'
}
</EuiText>
<EuiSpacer size="s" />
<EuiCheckbox
id={'includeReferencesDeep'}
label={
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.includeReferencesDeepLabel"
defaultMessage="Duplicate related objects"
/>
}
checked={isIncludeReferencesDeepChecked}
onChange={this.changeIncludeReferencesDeep}
/>
</>
</EuiFormRow>

<EuiSpacer size="m" />
{ignoredSeletedObjectsLength === 0 ? null : ignoreSomeObjectsChildren}
{ignoredSelectedObjectsLength === 0 ? null : ignoreSomeObjectsChildren}
<p>
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.tableTitle"
Expand All @@ -257,7 +284,7 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
</p>
<EuiSpacer size="m" />
<EuiInMemoryTable
items={includedSeletedObjects}
items={includedSelectedObjects}
columns={[
{
field: 'type',
Expand Down Expand Up @@ -302,13 +329,13 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
<EuiButton
fill
data-test-subj="copyConfirmButton"
onClick={() => this.copySavedObjects(includedSeletedObjects)}
onClick={() => this.copySavedObjects(includedSelectedObjects)}
isLoading={this.state.isLoading}
disabled={!confirmCopyButtonEnabled}
>
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.confirmButtonLabel"
defaultMessage="Confirm copy"
defaultMessage="Duplicate"
/>
</EuiButton>
</EuiModalFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ export const Header = ({

<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="baseline" gutterSize="m" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
data-test-subj="copyObjects"
onClick={onCopy}
disabled={selectedCount === 0}
>
<FormattedMessage
id="savedObjectsManagement.objectsTable.header.duplicateAllButtonLabel"
defaultMessage="Duplicate All"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
Expand Down Expand Up @@ -99,20 +112,6 @@ export const Header = ({
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
iconType="copy"
data-test-subj="copyObjects"
onClick={onCopy}
disabled={selectedCount === 0}
>
<FormattedMessage
id="savedObjectsManagement.objectsTable.header.copyButtonLabel"
defaultMessage="Copy"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty size="s" iconType="refresh" onClick={onRefresh}>
<FormattedMessage
Expand Down
Loading

0 comments on commit 5e472e1

Please sign in to comment.