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>
  • Loading branch information
yuye-aws authored and ruanyl committed Sep 15, 2023
1 parent 7605fe9 commit bfd5415
Show file tree
Hide file tree
Showing 18 changed files with 761 additions and 93 deletions.
584 changes: 576 additions & 8 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
Expand Up @@ -124,7 +124,7 @@ describe('SavedObjectTypeRegistry', () => {
users: ['user1'],
groups: ['group1'],
};
const result = ACL.genereateGetPermittedSavedObjectsQueryDSL(['read'], principals, 'workspace');
const result = ACL.generateGetPermittedSavedObjectsQueryDSL(['read'], principals, 'workspace');
expect(result).toEqual({
query: {
bool: {
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/saved_objects/permission_control/acl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class ACL {
/**
* generate query DSL by the specific conditions, used for fetching saved objects from the saved objects index
*/
public static genereateGetPermittedSavedObjectsQueryDSL(
public static generateGetPermittedSavedObjectsQueryDSL(
permissionTypes: string[],
principals: Principals,
savedObjectType?: string | string[]
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/saved_objects/permission_control/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export class SavedObjectsPermissionControl {
permissionModes: SavedObjectsPermissionModes
) {
const principals = this.getPrincipalsFromRequest(request);
const queryDSL = ACL.genereateGetPermittedSavedObjectsQueryDSL(permissionModes, principals, [
const queryDSL = ACL.generateGetPermittedSavedObjectsQueryDSL(permissionModes, principals, [
WORKSPACE_TYPE,
]);
const repository = this.getInternalRepository();
Expand Down
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,12 +23,13 @@ import {
EuiSpacer,
EuiComboBox,
EuiFormRow,
EuiSwitch,
EuiCheckbox,
EuiComboBoxOptionOption,
EuiInMemoryTable,
EuiToolTip,
EuiIcon,
EuiCallOut,
EuiText,
} from '@elastic/eui';
import { WorkspaceAttribute, WorkspaceStart } from 'opensearch-dashboards/public';
import { i18n } from '@osd/i18n';
Expand All @@ -46,11 +47,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 @@ -65,7 +67,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 @@ -79,21 +81,21 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
};

async componentDidMount() {
const { workspaces } = this.props;
const workspaceList = workspaces.workspaceList$;
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 @@ -152,18 +154,18 @@ 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) =>
const includedSelectedObjects = allSelectedObjects.filter((item) =>
!!targetWorkspaceId && !!item.workspaces
? !item.workspaces.includes(targetWorkspaceId)
: true && item.type !== SAVED_OBJECT_TYPE_WORKSAPCE
: item.type !== SAVED_OBJECT_TYPE_WORKSAPCE
);
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 @@ -175,7 +177,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 @@ -187,9 +189,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 @@ -207,7 +209,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 @@ -218,36 +224,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 @@ -256,7 +287,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 @@ -301,13 +332,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
Loading

0 comments on commit bfd5415

Please sign in to comment.