Skip to content

Commit

Permalink
feat: duplicate all and single objects (#121)
Browse files Browse the repository at this point in the history
* implement all duplicate copy modal

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

* add spacer after checkbox list

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

* add fail message for copy saved objects

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

* change title wording to manage library

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

* single duplicate

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

* change wording

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

* remove comment

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

* bug fix: keep selected saved objects info when cancel duplicate all

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

* fix typo

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

* use icu syntax in copy message

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

* bug fix: keep selected saved objects info when cancel duplicate single

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

* set current workspace as the first option

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

* update snapshot

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

* resolve conflict

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

* update snapshot

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

* bug fix for saved object table

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

* update snapshot

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

* remove unused file

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

* change i18n constant

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

* remove empty push

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

* hide duplicate when workspace is disabled

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

* update snapshots

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

---------

Signed-off-by: yuye-aws <yuyezhu@amazon.com>
  • Loading branch information
yuye-aws authored Sep 11, 2023
1 parent c6dc1a6 commit 6231768
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 64 deletions.
6 changes: 3 additions & 3 deletions src/plugins/saved_objects_management/public/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

import { i18n } from '@osd/i18n';

export const ALL_LIBRARY_OBJECTS_WORDINGS = i18n.translate(
'savedObjectsManagement.allLibraryObjects',
export const MANAGE_LIBRARY_TITLE_WORDINGS = i18n.translate(
'savedObjectsManagement.manageLibrary',
{
defaultMessage: 'All library objects',
defaultMessage: 'Manage library',
}
);

Expand Down

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 @@ -11,7 +11,7 @@

import React from 'react';
import { FormattedMessage } from '@osd/i18n/react';

import { groupBy } from 'lodash';
import {
EuiButton,
EuiButtonEmpty,
Expand All @@ -36,6 +36,7 @@ import { i18n } from '@osd/i18n';
import { SavedObjectWithMetadata } from '../../../types';
import { getSavedObjectLabel } from '../../../lib';
import { SAVED_OBJECT_TYPE_WORKSAPCE } from '../../../constants';
import { CopyState } from '../';

type WorkspaceOption = EuiComboBoxOptionOption<WorkspaceAttribute>;

Expand All @@ -47,6 +48,7 @@ interface Props {
targetWorkspace: string
) => Promise<void>;
onClose: () => void;
copyState: CopyState;
getCopyWorkspaces: () => Promise<WorkspaceAttribute[]>;
selectedSavedObjects: SavedObjectWithMetadata[];
}
Expand All @@ -58,6 +60,11 @@ interface State {
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 SavedObjectsCopyModal extends React.Component<Props, State> {
Expand All @@ -73,33 +80,56 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
targetWorkspaceOption: [],
isLoading: false,
isIncludeReferencesDeepChecked: true,
savedObjectTypeInfoMap: new Map<string, [number, boolean]>(),
};
}

workspaceToOption = (workspace: WorkspaceAttribute): WorkspaceOption => {
return { label: workspace.name, key: workspace.id, value: workspace };
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, getCopyWorkspaces } = this.props;
const workspaceList = await getCopyWorkspaces();
const currentWorkspace = workspaces.currentWorkspace$;
const currentWorkspace = workspaces.currentWorkspace$.value;
const currentWorkspaceName = currentWorkspace?.name;

if (!!currentWorkspace?.value?.name) {
const currentWorkspaceName = currentWorkspace.value.name;
const filteredWorkspaceOptions = workspaceList
.map(this.workspaceToOption)
.filter((item: WorkspaceOption) => item.label !== currentWorkspaceName);
this.setState({
workspaceOptions: filteredWorkspaceOptions,
allWorkspaceOptions: filteredWorkspaceOptions,
});
} else {
const allWorkspaceOptions = workspaceList.map(this.workspaceToOption);
this.setState({
workspaceOptions: allWorkspaceOptions,
allWorkspaceOptions,
});
// current workspace is the first option
const workspaceOptions = [
...(currentWorkspace ? [this.workspaceToOption(currentWorkspace, currentWorkspaceName)] : []),
...workspaceList
.filter((workspace: WorkspaceAttribute) => workspace.name !== currentWorkspaceName)
.map((workspace: WorkspaceAttribute) =>
this.workspaceToOption(workspace, currentWorkspaceName)
),
];

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

const { copyState } = this.props;
if (copyState === CopyState.All) {
const { allSelectedObjects } = this.state;
const categorizedObjects = groupBy(allSelectedObjects, (object) => object.type);
const savedObjectTypeInfoMap = new Map<string, [number, boolean]>();
for (const [savedObjectType, savedObjects] of Object.entries(categorizedObjects)) {
savedObjectTypeInfoMap.set(savedObjectType, [savedObjects.length, true]);
}
this.setState({ savedObjectTypeInfoMap });
}

this.isMounted = true;
Expand Down Expand Up @@ -149,26 +179,93 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
}));
};

changeIncludeSavedObjectType = (savedObjectType: string) => {
const { savedObjectTypeInfoMap } = this.state;
const savedObjectTypeInfo = savedObjectTypeInfoMap.get(savedObjectType);
if (savedObjectTypeInfo) {
const [count, checked] = savedObjectTypeInfo;
savedObjectTypeInfoMap.set(savedObjectType, [count, !checked]);
this.setState({ savedObjectTypeInfoMap });
}
};

renderCopyObjectCategory = (
savedObjectType: string,
savedObjectTypeCount: number,
savedObjectTypeChecked: boolean
) => {
return (
<EuiCheckbox
id={'includeSavedObjectType.' + savedObjectType}
key={savedObjectType}
label={
<FormattedMessage
id={'savedObjectsManagement.objectsTable.copyModal.savedObjectType.' + savedObjectType}
defaultMessage={
capitalizeFirstLetter(savedObjectType) + ` (${savedObjectTypeCount.toString()})`
}
/>
}
checked={savedObjectTypeChecked}
onChange={() => this.changeIncludeSavedObjectType(savedObjectType)}
/>
);
};

renderCopyObjectCategories = () => {
const { savedObjectTypeInfoMap } = this.state;
const checkboxList: JSX.Element[] = [];
savedObjectTypeInfoMap.forEach(
([savedObjectTypeCount, savedObjectTypeChecked], savedObjectType) =>
checkboxList.push(
this.renderCopyObjectCategory(
savedObjectType,
savedObjectTypeCount,
savedObjectTypeChecked
)
)
);
return checkboxList;
};

isSavedObjectTypeIncluded = (savedObjectType: string) => {
const { savedObjectTypeInfoMap } = this.state;
const savedObjectTypeInfo = savedObjectTypeInfoMap.get(savedObjectType);
return savedObjectTypeInfo && savedObjectTypeInfo[1];
};

render() {
const {
workspaceOptions,
targetWorkspaceOption,
isIncludeReferencesDeepChecked,
allSelectedObjects,
} = this.state;
const { copyState } = this.props;
const targetWorkspaceId = targetWorkspaceOption?.at(0)?.key;
const includedSelectedObjects = allSelectedObjects.filter((item) =>
let selectedObjects = allSelectedObjects;
if (copyState === CopyState.All) {
selectedObjects = selectedObjects.filter((item) => this.isSavedObjectTypeIncluded(item.type));
}
const includedSelectedObjects = selectedObjects.filter((item) =>
!!targetWorkspaceId && !!item.workspaces
? !item.workspaces.includes(targetWorkspaceId)
: item.type !== SAVED_OBJECT_TYPE_WORKSAPCE
);
const ignoredSelectedObjectsLength = allSelectedObjects.length - includedSelectedObjects.length;

const ignoredSelectedObjectsLength = selectedObjects.length - includedSelectedObjects.length;

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

const confirmMessageForAllObjects = `Duplicate (${includedSelectedObjects.length})`;
const confirmMessageForSingleOrSelectedObjects = 'Duplicate';
const confirmMessage =
copyState === CopyState.All
? confirmMessageForAllObjects
: confirmMessageForSingleOrSelectedObjects;
const warningMessageForOnlyOneSavedObject = (
<p>
<b style={{ color: '#000' }}>1</b> saved object will <b style={{ color: '#000' }}>not</b> be
Expand All @@ -179,7 +276,7 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
<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 worksapces themselves.
selected workspace or they are workspaces themselves.
</p>
);

Expand Down Expand Up @@ -209,11 +306,12 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
<EuiModalHeaderTitle>
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.title"
defaultMessage={
'Duplicate ' +
allSelectedObjects.length.toString() +
(allSelectedObjects.length > 1 ? ' objects?' : ' object?')
}
defaultMessage="Duplicate {copyState, select, all {all objects} other {{objectCount, plural, =1 {{objectName}} other {# objects}}}}?"
values={{
copyState,
objectName: allSelectedObjects[0].meta.title,
objectCount: allSelectedObjects.length,
}}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
Expand Down Expand Up @@ -246,6 +344,8 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
</EuiFormRow>

<EuiSpacer size="m" />
{copyState && this.renderCopyObjectCategories()}
{copyState && <EuiSpacer size="m" />}

<EuiFormRow
fullWidth
Expand Down Expand Up @@ -338,7 +438,7 @@ export class SavedObjectsCopyModal extends React.Component<Props, State> {
>
<FormattedMessage
id="savedObjectsManagement.objectsTable.copyModal.confirmButtonLabel"
defaultMessage="Duplicate"
defaultMessage={confirmMessage}
/>
</EuiButton>
</EuiModalFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const Header = ({
onRefresh,
filteredCount,
title,
selectedCount,
objectCount,
hideImport = false,
showDuplicateAll = false,
}: {
Expand All @@ -57,7 +57,7 @@ export const Header = ({
onRefresh: () => void;
filteredCount: number;
title: string;
selectedCount: number;
objectCount: number;
hideImport: boolean;
showDuplicateAll: boolean;
}) => (
Expand All @@ -77,7 +77,7 @@ export const Header = ({
size="s"
data-test-subj="copyObjects"
onClick={onCopy}
disabled={selectedCount === 0}
disabled={objectCount === 0}
>
<FormattedMessage
id="savedObjectsManagement.objectsTable.header.duplicateAllButtonLabel"
Expand Down
Loading

0 comments on commit 6231768

Please sign in to comment.