Skip to content

Commit

Permalink
Spaces - Copy Saved Objects to Spaces UI (#39002)
Browse files Browse the repository at this point in the history
  • Loading branch information
legrego authored Aug 23, 2019
1 parent c145a33 commit 6a9844c
Show file tree
Hide file tree
Showing 45 changed files with 3,117 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ import {
importLegacyFile,
resolveImportErrors,
logLegacyImport,
processImportResponse,
getDefaultTitle,
} from '../../../../lib';
import { processImportResponse } from '../../../../lib/process_import_response';
import {
resolveSavedObjects,
resolveSavedSearches,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import chrome from 'ui/chrome';
import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management';
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';

Expand Down Expand Up @@ -73,6 +74,12 @@ class TableUI extends PureComponent {
parseErrorMessage: null,
isExportPopoverOpen: false,
isIncludeReferencesDeepChecked: true,
activeAction: null,
}

constructor(props) {
super(props);
this.extraActions = SavedObjectsManagementActionRegistry.get();
}

onChange = ({ query, error }) => {
Expand Down Expand Up @@ -238,6 +245,24 @@ class TableUI extends PureComponent {
icon: 'kqlSelector',
onClick: object => onShowRelationships(object),
},
...this.extraActions.map(action => {
return {
...action.euiAction,
onClick: (object) => {
this.setState({
activeAction: action
});

action.registerOnFinishCallback(() => {
this.setState({
activeAction: null,
});
});

action.euiAction.onClick(object);
}
};
})
],
},
];
Expand Down Expand Up @@ -269,8 +294,11 @@ class TableUI extends PureComponent {
</EuiButton>
);

const activeActionContents = this.state.activeAction ? this.state.activeAction.render() : null;

return (
<Fragment>
{activeActionContents}
<EuiSearchBar
box={{ 'data-test-subj': 'savedObjectSearchBar' }}
filters={filters}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,38 @@
* under the License.
*/

export function processImportResponse(response) {
import {
SavedObjectsImportResponse,
SavedObjectsImportConflictError,
SavedObjectsImportUnsupportedTypeError,
SavedObjectsImportMissingReferencesError,
SavedObjectsImportUnknownError,
SavedObjectsImportError,
} from 'src/core/server';

export interface ProcessedImportResponse {
failedImports: Array<{
obj: Pick<SavedObjectsImportError, 'id' | 'type' | 'title'>;
error:
| SavedObjectsImportConflictError
| SavedObjectsImportUnsupportedTypeError
| SavedObjectsImportMissingReferencesError
| SavedObjectsImportUnknownError;
}>;
unmatchedReferences: Array<{
existingIndexPatternId: string;
list: Array<Record<string, any>>;
newIndexPatternId: string | undefined;
}>;
status: 'success' | 'idle';
importCount: number;
conflictedSavedObjectsLinkedToSavedSearches: undefined;
conflictedSearchDocs: undefined;
}

export function processImportResponse(
response: SavedObjectsImportResponse
): ProcessedImportResponse {
// Go through the failures and split between unmatchedReferences and failedImports
const failedImports = [];
const unmatchedReferences = new Map();
Expand All @@ -29,7 +60,9 @@ export function processImportResponse(response) {
// Currently only supports resolving references on index patterns
const indexPatternRefs = error.references.filter(ref => ref.type === 'index-pattern');
for (const missingReference of indexPatternRefs) {
const conflict = unmatchedReferences.get(`${missingReference.type}:${missingReference.id}`) || {
const conflict = unmatchedReferences.get(
`${missingReference.type}:${missingReference.id}`
) || {
existingIndexPatternId: missingReference.id,
list: [],
newIndexPatternId: undefined,
Expand All @@ -44,9 +77,11 @@ export function processImportResponse(response) {
unmatchedReferences: Array.from(unmatchedReferences.values()),
// Import won't be successful in the scenario unmatched references exist, import API returned errors of type unknown or import API
// returned errors of type missing_references.
status: unmatchedReferences.size === 0 && !failedImports.some(issue => issue.error.type === 'conflict')
? 'success'
: 'idle',
status:
unmatchedReferences.size === 0 &&
!failedImports.some(issue => issue.error.type === 'conflict')
? 'success'
: 'idle',
importCount: response.successCount,
conflictedSavedObjectsLinkedToSavedSearches: undefined,
conflictedSearchDocs: undefined,
Expand Down
29 changes: 29 additions & 0 deletions src/legacy/ui/public/management/saved_objects_management/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry';
export {
SavedObjectsManagementAction,
SavedObjectsManagementRecord,
SavedObjectsManagementRecordReference,
} from './saved_objects_management_action';
export {
processImportResponse,
ProcessedImportResponse,
} from '../../../../core_plugins/kibana/public/management/sections/objects/lib/process_import_response';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { ReactNode } from '@elastic/eui/node_modules/@types/react';

export interface SavedObjectsManagementRecordReference {
type: string;
id: string;
name: string;
}
export interface SavedObjectsManagementRecord {
type: string;
id: string;
meta: {
icon: string;
title: string;
};
references: SavedObjectsManagementRecordReference[];
}

export abstract class SavedObjectsManagementAction {
public abstract render: () => ReactNode;
public abstract id: string;
public abstract euiAction: {
name: string;
description: string;
icon: string;
type: string;
available?: (item: SavedObjectsManagementRecord) => boolean;
enabled?: (item: SavedObjectsManagementRecord) => boolean;
onClick?: (item: SavedObjectsManagementRecord) => void;
render?: (item: SavedObjectsManagementRecord) => any;
};

private callbacks: Function[] = [];

protected record: SavedObjectsManagementRecord | null = null;

public registerOnFinishCallback(callback: Function) {
this.callbacks.push(callback);
}

protected start(record: SavedObjectsManagementRecord) {
this.record = record;
}

protected finish() {
this.record = null;
this.callbacks.forEach(callback => callback());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry';
import { SavedObjectsManagementAction } from './saved_objects_management_action';

describe('SavedObjectsManagementActionRegistry', () => {
it('allows actions to be registered and retrieved', () => {
const action = { id: 'foo' } as SavedObjectsManagementAction;
SavedObjectsManagementActionRegistry.register(action);
expect(SavedObjectsManagementActionRegistry.get()).toContain(action);
});

it('requires an "id" property', () => {
expect(() =>
SavedObjectsManagementActionRegistry.register({} as SavedObjectsManagementAction)
).toThrowErrorMatchingInlineSnapshot(`"Saved Objects Management Actions must have an id"`);
});

it('does not allow actions with duplicate ids to be registered', () => {
const action = { id: 'my-action' } as SavedObjectsManagementAction;
SavedObjectsManagementActionRegistry.register(action);
expect(() =>
SavedObjectsManagementActionRegistry.register(action)
).toThrowErrorMatchingInlineSnapshot(
`"Saved Objects Management Action with id 'my-action' already exists"`
);
});

it('#has returns true when an action with a matching ID exists', () => {
const action = { id: 'existing-action' } as SavedObjectsManagementAction;
SavedObjectsManagementActionRegistry.register(action);
expect(SavedObjectsManagementActionRegistry.has('existing-action')).toEqual(true);
});

it(`#has returns false when an action with doesn't exist`, () => {
expect(SavedObjectsManagementActionRegistry.has('missing-action')).toEqual(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SavedObjectsManagementAction } from './saved_objects_management_action';

const actions: Map<string, SavedObjectsManagementAction> = new Map();

export const SavedObjectsManagementActionRegistry = {
register: (action: SavedObjectsManagementAction) => {
if (!action.id) {
throw new TypeError('Saved Objects Management Actions must have an id');
}
if (actions.has(action.id)) {
throw new Error(`Saved Objects Management Action with id '${action.id}' already exists`);
}
actions.set(action.id, action);
},

has: (actionId: string) => actions.has(actionId),

get: () => Array.from(actions.values()),
};
1 change: 1 addition & 0 deletions x-pack/dev-tools/jest/create_jest_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath,
'\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`,
'^test_utils/enzyme_helpers': `${xPackKibanaDirectory}/test_utils/enzyme_helpers.tsx`,
'^test_utils/find_test_subject': `${xPackKibanaDirectory}/test_utils/find_test_subject.ts`,
},
setupFiles: [
`${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`,
Expand Down
7 changes: 7 additions & 0 deletions x-pack/legacy/plugins/spaces/common/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export type GetSpacePurpose = 'any' | 'copySavedObjectsIntoSpace';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import {
SavedObjectsManagementAction,
SavedObjectsManagementRecord,
} from 'ui/management/saved_objects_management';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space';
import { Space } from '../../../common/model/space';
import { SpacesManager } from '../spaces_manager';

export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagementAction {
public id: string = 'copy_saved_objects_to_space';

public euiAction = {
name: i18n.translate('xpack.spaces.management.copyToSpace.actionTitle', {
defaultMessage: 'Copy to space',
}),
description: i18n.translate('xpack.spaces.management.copyToSpace.actionDescription', {
defaultMessage: 'Copy this saved object to one or more spaces',
}),
icon: 'spacesApp',
type: 'icon',
onClick: (object: SavedObjectsManagementRecord) => {
this.start(object);
},
};

constructor(private readonly spacesManager: SpacesManager, private readonly activeSpace: Space) {
super();
}

public render = () => {
if (!this.record) {
throw new Error('No record available! `render()` was likely called before `start()`.');
}
return (
<CopySavedObjectsToSpaceFlyout
onClose={this.onClose}
savedObject={this.record}
spacesManager={this.spacesManager}
activeSpace={this.activeSpace}
toastNotifications={toastNotifications}
/>
);
};

private onClose = () => {
this.finish();
};
}
Loading

0 comments on commit 6a9844c

Please sign in to comment.