mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Migrate SO management section to NP (#61700)
* move libs to new plugin * adapt libs to use NP apis * add required plugins * add get_allowed_types route * move object_view components * add service registry * migrate table header component * migrate table component * migrate saved_objects_table component * remove migrated legacy files * fix re-export from legacy management + section label * migrate services registration * adapt management section mock * fix imports * migrate flyout component * migrate relationships component * migrate saved_objects_table tests * migrate breadcrumb * add redirect if unauthorized check * migrate translations to new savedObjectsManagement prefix * remove obsolete translations * convert action registry to service pattern * wire extra actions * remove importAndExportableTypes from injected vars * handle newIndexPatternUrl * remove duplicate dashboard dependency * remove old TODO * remove old TODO * properly mock lodash in tests * add async management section loading * expose createSavedSearchesLoader from discover plugin contract * address most review comments * fix merge conflicts
This commit is contained in:
parent
1199c8c8b0
commit
358d13919b
110 changed files with 2182 additions and 1687 deletions
|
@ -48,6 +48,7 @@ export { overlayServiceMock } from './overlays/overlay_service.mock';
|
|||
export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
||||
export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock';
|
||||
export { scopedHistoryMock } from './application/scoped_history.mock';
|
||||
export { applicationServiceMock } from './application/application_service.mock';
|
||||
|
||||
function createCoreSetupMock({
|
||||
basePath = '',
|
||||
|
@ -62,9 +63,8 @@ function createCoreSetupMock({
|
|||
application: applicationServiceMock.createSetupContract(),
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
|
||||
getStartServices: jest.fn<Promise<[ReturnType<typeof createCoreStartMock>, object, any]>, []>(
|
||||
() =>
|
||||
Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract])
|
||||
getStartServices: jest.fn<Promise<[ReturnType<typeof createCoreStartMock>, any, any]>, []>(() =>
|
||||
Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract])
|
||||
),
|
||||
http: httpServiceMock.createSetupContract({ basePath }),
|
||||
notifications: notificationServiceMock.createSetupContract(),
|
||||
|
|
|
@ -36,7 +36,6 @@ export interface SavedObjectsLegacyService {
|
|||
getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient'];
|
||||
SavedObjectsClient: typeof SavedObjectsClient;
|
||||
types: string[];
|
||||
importAndExportableTypes: string[];
|
||||
schema: SavedObjectsSchema;
|
||||
getSavedObjectsRepository(...rest: any[]): any;
|
||||
importExport: {
|
||||
|
|
|
@ -2084,8 +2084,6 @@ export interface SavedObjectsLegacyService {
|
|||
// (undocumented)
|
||||
getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient'];
|
||||
// (undocumented)
|
||||
importAndExportableTypes: string[];
|
||||
// (undocumented)
|
||||
importExport: {
|
||||
objectLimit: number;
|
||||
importSavedObjects(options: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse>;
|
||||
|
|
|
@ -20,10 +20,7 @@
|
|||
export function injectVars(server) {
|
||||
const serverConfig = server.config();
|
||||
|
||||
const { importAndExportableTypes } = server.savedObjects;
|
||||
|
||||
return {
|
||||
importAndExportableTypes,
|
||||
autocompleteTerminateAfter: serverConfig.get('kibana.autocompleteTerminateAfter'),
|
||||
autocompleteTimeout: serverConfig.get('kibana.autocompleteTimeout'),
|
||||
};
|
||||
|
|
|
@ -20,4 +20,4 @@
|
|||
export {
|
||||
ProcessedImportResponse,
|
||||
processImportResponse,
|
||||
} from './management/sections/objects/lib/process_import_response';
|
||||
} from '../../../../plugins/saved_objects_management/public/lib';
|
||||
|
|
|
@ -17,66 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { SavedObjectLoader } from '../../../../../plugins/saved_objects/public';
|
||||
import { createSavedSearchesLoader } from '../../../../../plugins/discover/public';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
|
||||
/**
|
||||
* This registry is used for the editing mode of Saved Searches, Visualizations,
|
||||
* Dashboard and Time Lion saved objects.
|
||||
*/
|
||||
interface SavedObjectRegistryEntry {
|
||||
id: string;
|
||||
service: SavedObjectLoader;
|
||||
title: string;
|
||||
}
|
||||
const registry = npSetup.plugins.savedObjectsManagement?.serviceRegistry;
|
||||
|
||||
export interface ISavedObjectsManagementRegistry {
|
||||
register(service: SavedObjectRegistryEntry): void;
|
||||
all(): SavedObjectRegistryEntry[];
|
||||
get(id: string): SavedObjectRegistryEntry | undefined;
|
||||
}
|
||||
|
||||
const registry: SavedObjectRegistryEntry[] = [];
|
||||
|
||||
export const savedObjectManagementRegistry: ISavedObjectsManagementRegistry = {
|
||||
register: (service: SavedObjectRegistryEntry) => {
|
||||
registry.push(service);
|
||||
},
|
||||
all: () => {
|
||||
return registry;
|
||||
},
|
||||
get: (id: string) => {
|
||||
return _.find(registry, { id });
|
||||
},
|
||||
};
|
||||
|
||||
const services = {
|
||||
savedObjectsClient: npStart.core.savedObjects.client,
|
||||
indexPatterns: npStart.plugins.data.indexPatterns,
|
||||
search: npStart.plugins.data.search,
|
||||
chrome: npStart.core.chrome,
|
||||
overlays: npStart.core.overlays,
|
||||
};
|
||||
|
||||
savedObjectManagementRegistry.register({
|
||||
id: 'savedVisualizations',
|
||||
service: npStart.plugins.visualizations.savedVisualizationsLoader,
|
||||
title: 'visualizations',
|
||||
});
|
||||
|
||||
savedObjectManagementRegistry.register({
|
||||
id: 'savedDashboards',
|
||||
service: npStart.plugins.dashboard.getSavedDashboardLoader(),
|
||||
title: i18n.translate('kbn.dashboard.savedDashboardsTitle', {
|
||||
defaultMessage: 'dashboards',
|
||||
}),
|
||||
});
|
||||
|
||||
savedObjectManagementRegistry.register({
|
||||
id: 'savedSearches',
|
||||
service: createSavedSearchesLoader(services),
|
||||
title: 'searches',
|
||||
});
|
||||
export const savedObjectManagementRegistry = registry!;
|
||||
|
|
|
@ -17,5 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './objects';
|
||||
import './index_patterns';
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<kbn-management-app section="kibana/objects">
|
||||
<kbn-management-objects>
|
||||
<div id="reactSavedObjectsTable"></div>
|
||||
</kbn-management-objects>
|
||||
</kbn-management-app>
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* 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 { savedObjectManagementRegistry } from '../../saved_object_registry';
|
||||
import objectIndexHTML from './_objects.html';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import chrome from 'ui/chrome';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { ObjectsTable } from './components/objects_table';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { get } from 'lodash';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { getIndexBreadcrumbs } from './breadcrumbs';
|
||||
|
||||
const REACT_OBJECTS_TABLE_DOM_ELEMENT_ID = 'reactSavedObjectsTable';
|
||||
|
||||
function updateObjectsTable($scope, $injector) {
|
||||
const indexPatterns = npStart.plugins.data.indexPatterns;
|
||||
const $http = $injector.get('$http');
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const config = $injector.get('config');
|
||||
|
||||
const savedObjectsClient = npStart.core.savedObjects.client;
|
||||
const services = savedObjectManagementRegistry.all().map(obj => obj.service);
|
||||
const uiCapabilites = npStart.core.application.capabilities;
|
||||
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_OBJECTS_TABLE_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<ObjectsTable
|
||||
savedObjectsClient={savedObjectsClient}
|
||||
confirmModalPromise={npStart.core.overlays.openConfirm}
|
||||
services={services}
|
||||
indexPatterns={indexPatterns}
|
||||
$http={$http}
|
||||
perPageConfig={config.get('savedObjects:perPage')}
|
||||
basePath={chrome.getBasePath()}
|
||||
newIndexPatternUrl={kbnUrl.eval('#/management/kibana/index_pattern')}
|
||||
uiCapabilities={uiCapabilites}
|
||||
goInspectObject={object => {
|
||||
if (object.meta.editUrl) {
|
||||
kbnUrl.change(object.meta.editUrl);
|
||||
$scope.$apply();
|
||||
}
|
||||
}}
|
||||
canGoInApp={object => {
|
||||
const { inAppUrl } = object.meta;
|
||||
return inAppUrl && get(uiCapabilites, inAppUrl.uiCapabilitiesPath);
|
||||
}}
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function destroyObjectsTable() {
|
||||
const node = document.getElementById(REACT_OBJECTS_TABLE_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/objects', {
|
||||
template: objectIndexHTML,
|
||||
k7Breadcrumbs: getIndexBreadcrumbs,
|
||||
requireUICapability: 'management.kibana.objects',
|
||||
})
|
||||
.when('/management/kibana/objects/:service', {
|
||||
redirectTo: '/management/kibana/objects',
|
||||
});
|
||||
|
||||
uiModules.get('apps/management').directive('kbnManagementObjects', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'managementObjectsController',
|
||||
controller: function($scope, $injector) {
|
||||
updateObjectsTable($scope, $injector);
|
||||
$scope.$on('$destroy', destroyObjectsTable);
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
<kbn-management-app section="kibana/objects" data-test-subj="savedObjectsEdit">
|
||||
<kbn-management-objects-view>
|
||||
<div id="reactSavedObjectsView"></div>
|
||||
</kbn-management-objects-view>
|
||||
</kbn-management-app>
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import 'angular';
|
||||
import 'angular-elastic/elastic';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import objectViewHTML from './_view.html';
|
||||
import { getViewBreadcrumbs } from './breadcrumbs';
|
||||
import { savedObjectManagementRegistry } from '../../saved_object_registry';
|
||||
import { SavedObjectEdition } from './saved_object_view';
|
||||
|
||||
const REACT_OBJECTS_VIEW_DOM_ELEMENT_ID = 'reactSavedObjectsView';
|
||||
|
||||
uiRoutes.when('/management/kibana/objects/:service/:id', {
|
||||
template: objectViewHTML,
|
||||
k7Breadcrumbs: getViewBreadcrumbs,
|
||||
requireUICapability: 'management.kibana.objects',
|
||||
});
|
||||
|
||||
function createReactView($scope, $routeParams) {
|
||||
const { service: serviceName, id: objectId, notFound } = $routeParams;
|
||||
|
||||
const { savedObjects, overlays, notifications, application } = npStart.core;
|
||||
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_OBJECTS_VIEW_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<SavedObjectEdition
|
||||
id={objectId}
|
||||
serviceName={serviceName}
|
||||
serviceRegistry={savedObjectManagementRegistry}
|
||||
savedObjectsClient={savedObjects.client}
|
||||
overlays={overlays}
|
||||
notifications={notifications}
|
||||
capabilities={application.capabilities}
|
||||
notFoundType={notFound}
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function destroyReactView() {
|
||||
const node = document.getElementById(REACT_OBJECTS_VIEW_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
uiModules
|
||||
.get('apps/management', ['monospaced.elastic'])
|
||||
.directive('kbnManagementObjectsView', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: function($scope, $routeParams) {
|
||||
createReactView($scope, $routeParams);
|
||||
$scope.$on('$destroy', destroyReactView);
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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 { MANAGEMENT_BREADCRUMB } from 'ui/management';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { savedObjectManagementRegistry } from '../../saved_object_registry';
|
||||
|
||||
export function getIndexBreadcrumbs() {
|
||||
return [
|
||||
MANAGEMENT_BREADCRUMB,
|
||||
{
|
||||
text: i18n.translate('kbn.management.savedObjects.indexBreadcrumb', {
|
||||
defaultMessage: 'Saved objects',
|
||||
}),
|
||||
href: '#/management/kibana/objects',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getViewBreadcrumbs($routeParams) {
|
||||
const serviceObj = savedObjectManagementRegistry.get($routeParams.service);
|
||||
const { service } = serviceObj;
|
||||
|
||||
return [
|
||||
...getIndexBreadcrumbs(),
|
||||
{
|
||||
text: i18n.translate('kbn.management.savedObjects.editBreadcrumb', {
|
||||
defaultMessage: 'Edit {savedObjectType}',
|
||||
values: { savedObjectType: service.type },
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 { ObjectsTable } from './objects_table';
|
|
@ -78,13 +78,8 @@ export function savedObjectsMixin(kbnServer, server) {
|
|||
|
||||
const provider = kbnServer.newPlatform.__internals.savedObjectsClientProvider;
|
||||
|
||||
const importAndExportableTypes = typeRegistry
|
||||
.getImportableAndExportableTypes()
|
||||
.map(type => type.name);
|
||||
|
||||
const service = {
|
||||
types: visibleTypes,
|
||||
importAndExportableTypes,
|
||||
SavedObjectsClient,
|
||||
SavedObjectsRepository,
|
||||
getSavedObjectsRepository: createRepository,
|
||||
|
|
|
@ -62,6 +62,7 @@ const createStartContract = (): Start => {
|
|||
},
|
||||
}),
|
||||
get: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
clearCache: jest.fn(),
|
||||
} as unknown) as IndexPatternsContract,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -37,6 +37,9 @@ const createStartContract = (): Start => {
|
|||
docViews: {
|
||||
DocViewer: jest.fn(() => null),
|
||||
},
|
||||
savedSearches: {
|
||||
createLoader: jest.fn(),
|
||||
},
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
|
|
@ -21,12 +21,14 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { auto } from 'angular';
|
||||
import { CoreSetup, Plugin } from 'kibana/public';
|
||||
import { SavedObjectLoader, SavedObjectKibanaServices } from '../../saved_objects/public';
|
||||
import { DocViewInput, DocViewInputFn, DocViewRenderProps } from './doc_views/doc_views_types';
|
||||
import { DocViewsRegistry } from './doc_views/doc_views_registry';
|
||||
import { DocViewTable } from './components/table/table';
|
||||
import { JsonCodeBlock } from './components/json_code_block/json_code_block';
|
||||
import { DocViewer } from './components/doc_viewer/doc_viewer';
|
||||
import { setDocViewsRegistry } from './services';
|
||||
import { createSavedSearchesLoader } from './saved_searches';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
@ -62,6 +64,13 @@ export interface DiscoverStart {
|
|||
*/
|
||||
DocViewer: React.ComponentType<DocViewRenderProps>;
|
||||
};
|
||||
savedSearches: {
|
||||
/**
|
||||
* Create a {@link SavedObjectLoader | loader} to handle the saved searches type.
|
||||
* @param services
|
||||
*/
|
||||
createLoader(services: SavedObjectKibanaServices): SavedObjectLoader;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,6 +114,9 @@ export class DiscoverPlugin implements Plugin<DiscoverSetup, DiscoverStart> {
|
|||
docViews: {
|
||||
DocViewer,
|
||||
},
|
||||
savedSearches: {
|
||||
createLoader: createSavedSearchesLoader,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,21 @@
|
|||
*/
|
||||
|
||||
import { ManagementSetup, ManagementStart } from '../types';
|
||||
import { ManagementSection } from '../management_section';
|
||||
|
||||
const createManagementSectionMock = (): jest.Mocked<PublicMethodsOf<ManagementSection>> => {
|
||||
return {
|
||||
registerApp: jest.fn(),
|
||||
getApp: jest.fn(),
|
||||
getAppsEnabled: jest.fn().mockReturnValue([]),
|
||||
};
|
||||
};
|
||||
|
||||
const createSetupContract = (): DeeplyMockedKeys<ManagementSetup> => ({
|
||||
sections: {
|
||||
register: jest.fn(),
|
||||
getSection: jest.fn(),
|
||||
getAllSections: jest.fn(),
|
||||
getSection: jest.fn().mockReturnValue(createManagementSectionMock()),
|
||||
getAllSections: jest.fn().mockReturnValue([]),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Header } from './header';
|
||||
export { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types';
|
|
@ -17,8 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObject, SavedObjectReference } from 'src/core/public';
|
||||
import { SavedObject } from 'src/core/types';
|
||||
|
||||
/**
|
||||
* The metadata injected into a {@link SavedObject | saved object} when returning
|
||||
* {@link SavedObjectWithMetadata | enhanced objects} from the plugin API endpoints.
|
||||
*/
|
||||
export interface SavedObjectMetadata {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
|
@ -26,31 +30,19 @@ export interface SavedObjectMetadata {
|
|||
inAppUrl?: { path: string; uiCapabilitiesPath: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link SavedObject | saved object} enhanced with meta properties used by the client-side plugin.
|
||||
*/
|
||||
export type SavedObjectWithMetadata<T = unknown> = SavedObject<T> & {
|
||||
meta: SavedObjectMetadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a relation between two {@link SavedObject | saved object}
|
||||
*/
|
||||
export interface SavedObjectRelation {
|
||||
id: string;
|
||||
type: string;
|
||||
relationship: 'child' | 'parent';
|
||||
meta: SavedObjectMetadata;
|
||||
}
|
||||
|
||||
export interface ObjectField {
|
||||
type: FieldType;
|
||||
name: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export type FieldType = 'text' | 'number' | 'boolean' | 'array' | 'json';
|
||||
|
||||
export interface FieldState {
|
||||
value?: any;
|
||||
invalid?: boolean;
|
||||
}
|
||||
|
||||
export interface SubmittedFormData {
|
||||
attributes: any;
|
||||
references: SavedObjectReference[];
|
||||
}
|
|
@ -3,5 +3,6 @@
|
|||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["home"]
|
||||
"requiredPlugins": ["home", "management", "data"],
|
||||
"optionalPlugins": ["dashboard", "visualizations", "discover"]
|
||||
}
|
||||
|
|
|
@ -22,10 +22,14 @@ import { SavedObjectsManagementPlugin } from './plugin';
|
|||
|
||||
export { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart } from './plugin';
|
||||
export {
|
||||
ISavedObjectsManagementActionRegistry,
|
||||
SavedObjectsManagementActionServiceSetup,
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
SavedObjectsManagementAction,
|
||||
SavedObjectsManagementRecord,
|
||||
ISavedObjectsManagementServiceRegistry,
|
||||
SavedObjectsManagementServiceRegistryEntry,
|
||||
} from './services';
|
||||
export { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new SavedObjectsManagementPlugin();
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SimpleSavedObject, SavedObjectReference } from '../../../../../../../../core/public';
|
||||
import { savedObjectsServiceMock } from '../../../../../../../../core/public/mocks';
|
||||
import { SimpleSavedObject, SavedObjectReference } from '../../../../core/public';
|
||||
import { savedObjectsServiceMock } from '../../../../core/public/mocks';
|
||||
import { createFieldList } from './create_field_list';
|
||||
|
||||
const savedObjectClientMock = savedObjectsServiceMock.createStartContract().client;
|
|
@ -18,10 +18,10 @@
|
|||
*/
|
||||
|
||||
import { forOwn, indexBy, isNumber, isBoolean, isPlainObject, isString } from 'lodash';
|
||||
import { SimpleSavedObject } from '../../../../../../../../core/public';
|
||||
import { castEsToKbnFieldTypeName } from '../../../../../../../../plugins/data/public';
|
||||
import { ObjectField } from '../types';
|
||||
import { SavedObjectLoader } from '../../../../../../../../plugins/saved_objects/public';
|
||||
import { SimpleSavedObject } from '../../../../core/public';
|
||||
import { castEsToKbnFieldTypeName } from '../../../data/public';
|
||||
import { ObjectField } from '../management_section/types';
|
||||
import { SavedObjectLoader } from '../../../saved_objects/public';
|
||||
|
||||
const maxRecursiveIterations = 20;
|
||||
|
|
@ -17,16 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
|
||||
export async function fetchExportByTypeAndSearch(
|
||||
http: HttpStart,
|
||||
types: string[],
|
||||
search: string | undefined,
|
||||
includeReferencesDeep: boolean = false
|
||||
): Promise<Blob> {
|
||||
return await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/saved_objects/_export',
|
||||
return http.post('/api/saved_objects/_export', {
|
||||
body: JSON.stringify({
|
||||
type: types,
|
||||
search,
|
|
@ -17,15 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
|
||||
export async function fetchExportObjects(
|
||||
http: HttpStart,
|
||||
objects: any[],
|
||||
includeReferencesDeep: boolean = false
|
||||
): Promise<Blob> {
|
||||
return await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/saved_objects/_export',
|
||||
return http.post('/api/saved_objects/_export', {
|
||||
body: JSON.stringify({
|
||||
objects,
|
||||
includeReferencesDeep,
|
|
@ -17,16 +17,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { SavedObjectsFindOptions } from 'src/core/public';
|
||||
import { HttpStart, SavedObjectsFindOptions } from 'src/core/public';
|
||||
import { keysToCamelCaseShallow } from './case_conversion';
|
||||
import { SavedObjectWithMetadata } from '../types';
|
||||
|
||||
export async function findObjects(findOptions: SavedObjectsFindOptions) {
|
||||
const response = await kfetch({
|
||||
method: 'GET',
|
||||
pathname: '/api/kibana/management/saved_objects/_find',
|
||||
query: findOptions as Record<string, any>,
|
||||
});
|
||||
|
||||
return keysToCamelCaseShallow(response);
|
||||
interface SavedObjectsFindResponse {
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
savedObjects: SavedObjectWithMetadata[];
|
||||
}
|
||||
|
||||
export async function findObjects(
|
||||
http: HttpStart,
|
||||
findOptions: SavedObjectsFindOptions
|
||||
): Promise<SavedObjectsFindResponse> {
|
||||
const response = await http.get<Record<string, any>>(
|
||||
'/api/kibana/management/saved_objects/_find',
|
||||
{
|
||||
query: findOptions as Record<string, any>,
|
||||
}
|
||||
);
|
||||
|
||||
return keysToCamelCaseShallow(response) as SavedObjectsFindResponse;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { HttpStart } from 'src/core/public';
|
||||
|
||||
interface GetAllowedTypesResponse {
|
||||
types: string[];
|
||||
}
|
||||
|
||||
export async function getAllowedTypes(http: HttpStart) {
|
||||
const response = await http.get<GetAllowedTypesResponse>(
|
||||
'/api/kibana/management/saved_objects/_allowed_types'
|
||||
);
|
||||
return response.types;
|
||||
}
|
|
@ -17,44 +17,43 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from '../../../../core/public/mocks';
|
||||
import { getRelationships } from './get_relationships';
|
||||
|
||||
describe('getRelationships', () => {
|
||||
it('should make an http request', async () => {
|
||||
const $http = jest.fn() as any;
|
||||
const basePath = 'test';
|
||||
let httpMock: ReturnType<typeof httpServiceMock.createSetupContract>;
|
||||
|
||||
await getRelationships('dashboard', '1', ['search', 'index-pattern'], $http, basePath);
|
||||
expect($http.mock.calls.length).toBe(1);
|
||||
beforeEach(() => {
|
||||
httpMock = httpServiceMock.createSetupContract();
|
||||
});
|
||||
|
||||
it('should make an http request', async () => {
|
||||
await getRelationships(httpMock, 'dashboard', '1', ['search', 'index-pattern']);
|
||||
expect(httpMock.get).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle successful responses', async () => {
|
||||
const $http = jest.fn().mockImplementation(() => ({ data: [1, 2] })) as any;
|
||||
const basePath = 'test';
|
||||
httpMock.get.mockResolvedValue([1, 2]);
|
||||
|
||||
const response = await getRelationships(
|
||||
'dashboard',
|
||||
'1',
|
||||
['search', 'index-pattern'],
|
||||
$http,
|
||||
basePath
|
||||
);
|
||||
const response = await getRelationships(httpMock, 'dashboard', '1', [
|
||||
'search',
|
||||
'index-pattern',
|
||||
]);
|
||||
expect(response).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should handle errors', async () => {
|
||||
const $http = jest.fn().mockImplementation(() => {
|
||||
httpMock.get.mockImplementation(() => {
|
||||
const err = new Error();
|
||||
(err as any).data = {
|
||||
error: 'Test error',
|
||||
statusCode: 500,
|
||||
};
|
||||
throw err;
|
||||
}) as any;
|
||||
const basePath = 'test';
|
||||
});
|
||||
|
||||
await expect(
|
||||
getRelationships('dashboard', '1', ['search', 'index-pattern'], $http, basePath)
|
||||
getRelationships(httpMock, 'dashboard', '1', ['search', 'index-pattern'])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Test error"`);
|
||||
});
|
||||
});
|
|
@ -17,36 +17,30 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IHttpService } from 'angular';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { get } from 'lodash';
|
||||
import { SavedObjectRelation } from '../types';
|
||||
|
||||
export async function getRelationships(
|
||||
http: HttpStart,
|
||||
type: string,
|
||||
id: string,
|
||||
savedObjectTypes: string[],
|
||||
$http: IHttpService,
|
||||
basePath: string
|
||||
savedObjectTypes: string[]
|
||||
): Promise<SavedObjectRelation[]> {
|
||||
const url = `${basePath}/api/kibana/management/saved_objects/relationships/${encodeURIComponent(
|
||||
const url = `/api/kibana/management/saved_objects/relationships/${encodeURIComponent(
|
||||
type
|
||||
)}/${encodeURIComponent(id)}`;
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url,
|
||||
params: {
|
||||
savedObjectTypes,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await $http<SavedObjectRelation[]>(options);
|
||||
return response?.data;
|
||||
} catch (resp) {
|
||||
const respBody = get(resp, 'data', {}) as any;
|
||||
const err = new Error(respBody.message || respBody.error || `${resp.status} Response`);
|
||||
return await http.get<SavedObjectRelation[]>(url, {
|
||||
query: {
|
||||
savedObjectTypes,
|
||||
},
|
||||
});
|
||||
} catch (respError) {
|
||||
const respBody = get(respError, 'data', {}) as any;
|
||||
const err = new Error(respBody.message || respBody.error || `${respError.status} Response`);
|
||||
|
||||
(err as any).statusCode = respBody.statusCode || resp.status;
|
||||
(err as any).statusCode = respBody.statusCode || respError.status;
|
||||
(err as any).body = respBody;
|
||||
|
||||
throw err;
|
|
@ -17,18 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IHttpService } from 'angular';
|
||||
import chrome from 'ui/chrome';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
|
||||
const apiBase = chrome.addBasePath('/api/kibana/management/saved_objects/scroll');
|
||||
export async function getSavedObjectCounts(
|
||||
$http: IHttpService,
|
||||
http: HttpStart,
|
||||
typesToInclude: string[],
|
||||
searchString: string
|
||||
searchString?: string
|
||||
): Promise<Record<string, number>> {
|
||||
const results = await $http.post<Record<string, number>>(`${apiBase}/counts`, {
|
||||
typesToInclude,
|
||||
searchString,
|
||||
});
|
||||
return results.data;
|
||||
return await http.post<Record<string, number>>(
|
||||
`/api/kibana/management/saved_objects/scroll/counts`,
|
||||
{ body: JSON.stringify({ typesToInclude, searchString }) }
|
||||
);
|
||||
}
|
|
@ -17,14 +17,18 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { HttpStart, SavedObjectsImportError } from 'src/core/public';
|
||||
|
||||
export async function importFile(file: Blob, overwriteAll: boolean = false) {
|
||||
interface ImportResponse {
|
||||
success: boolean;
|
||||
successCount: number;
|
||||
errors?: SavedObjectsImportError[];
|
||||
}
|
||||
|
||||
export async function importFile(http: HttpStart, file: File, overwriteAll: boolean = false) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/saved_objects/_import',
|
||||
return await http.post<ImportResponse>('/api/saved_objects/_import', {
|
||||
body: formData,
|
||||
headers: {
|
||||
// Important to be undefined, it forces proper headers to be set for FormData
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Capabilities } from '../../../../../../../../core/public';
|
||||
import { Capabilities } from '../../../../core/public';
|
||||
import { canViewInApp } from './in_app_url';
|
||||
|
||||
const createCapabilities = (sections: Record<string, any>): Capabilities => {
|
|
@ -43,3 +43,5 @@ export {
|
|||
export { getDefaultTitle } from './get_default_title';
|
||||
export { findObjects } from './find_objects';
|
||||
export { extractExportDetails, SavedObjectsExportResultDetails } from './extract_export_details';
|
||||
export { createFieldList } from './create_field_list';
|
||||
export { getAllowedTypes } from './get_allowed_types';
|
|
@ -17,11 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
|
||||
export async function logLegacyImport() {
|
||||
return await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/saved_objects/_log_legacy_import',
|
||||
});
|
||||
export async function logLegacyImport(http: HttpStart) {
|
||||
return http.post('/api/saved_objects/_log_legacy_import');
|
||||
}
|
|
@ -25,6 +25,6 @@ describe('getQueryText', () => {
|
|||
getTermClauses: () => [{ value: 'foo' }, { value: 'bar' }],
|
||||
getFieldClauses: () => [{ value: 'lala' }, { value: 'lolo' }],
|
||||
};
|
||||
expect(parseQuery({ ast })).toEqual({ queryText: 'foo bar', visibleTypes: 'lala' });
|
||||
expect(parseQuery({ ast } as any)).toEqual({ queryText: 'foo bar', visibleTypes: 'lala' });
|
||||
});
|
||||
});
|
|
@ -17,9 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function parseQuery(query: any) {
|
||||
let queryText;
|
||||
let visibleTypes;
|
||||
import { Query } from '@elastic/eui';
|
||||
|
||||
interface ParsedQuery {
|
||||
queryText?: string;
|
||||
visibleTypes?: string[];
|
||||
}
|
||||
|
||||
export function parseQuery(query: Query): ParsedQuery {
|
||||
let queryText: string | undefined;
|
||||
let visibleTypes: string[] | undefined;
|
||||
|
||||
if (query) {
|
||||
if (query.ast.getTermClauses().length) {
|
||||
|
@ -29,7 +36,7 @@ export function parseQuery(query: any) {
|
|||
.join(' ');
|
||||
}
|
||||
if (query.ast.getFieldClauses('type')) {
|
||||
visibleTypes = query.ast.getFieldClauses('type')[0].value;
|
||||
visibleTypes = query.ast.getFieldClauses('type')[0].value as string[];
|
||||
}
|
||||
}
|
||||
|
|
@ -17,14 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() }));
|
||||
|
||||
import { SavedObjectsImportUnknownError } from 'src/core/public';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { httpServiceMock } from '../../../../core/public/mocks';
|
||||
import { resolveImportErrors } from './resolve_import_errors';
|
||||
|
||||
const kfetchMock = kfetch as jest.Mock;
|
||||
|
||||
function getFormData(form: Map<string, any>) {
|
||||
const formData: Record<string, any> = {};
|
||||
for (const [key, val] of form.entries()) {
|
||||
|
@ -39,13 +35,20 @@ function getFormData(form: Map<string, any>) {
|
|||
|
||||
describe('resolveImportErrors', () => {
|
||||
const getConflictResolutions = jest.fn();
|
||||
let httpMock: ReturnType<typeof httpServiceMock.createSetupContract>;
|
||||
|
||||
beforeEach(() => {
|
||||
httpMock = httpServiceMock.createSetupContract();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const extractBodyFromCall = (index: number): Map<string, any> => {
|
||||
return (httpMock.post.mock.calls[index] as any)[1].body;
|
||||
};
|
||||
|
||||
test('works with empty import failures', async () => {
|
||||
const result = await resolveImportErrors({
|
||||
http: httpMock,
|
||||
getConflictResolutions,
|
||||
state: {
|
||||
importCount: 0,
|
||||
|
@ -62,6 +65,7 @@ Object {
|
|||
|
||||
test(`doesn't retry if only unknown failures are passed in`, async () => {
|
||||
const result = await resolveImportErrors({
|
||||
http: httpMock,
|
||||
getConflictResolutions,
|
||||
state: {
|
||||
importCount: 0,
|
||||
|
@ -98,7 +102,7 @@ Object {
|
|||
});
|
||||
|
||||
test('resolves conflicts', async () => {
|
||||
kfetchMock.mockResolvedValueOnce({
|
||||
httpMock.post.mockResolvedValueOnce({
|
||||
success: true,
|
||||
successCount: 1,
|
||||
});
|
||||
|
@ -107,6 +111,7 @@ Object {
|
|||
'a:2': false,
|
||||
});
|
||||
const result = await resolveImportErrors({
|
||||
http: httpMock,
|
||||
getConflictResolutions,
|
||||
state: {
|
||||
importCount: 0,
|
||||
|
@ -139,7 +144,8 @@ Object {
|
|||
"status": "success",
|
||||
}
|
||||
`);
|
||||
const formData = getFormData(kfetchMock.mock.calls[0][0].body);
|
||||
|
||||
const formData = getFormData(extractBodyFromCall(0));
|
||||
expect(formData).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"file": "undefined",
|
||||
|
@ -156,12 +162,13 @@ Object {
|
|||
});
|
||||
|
||||
test('resolves missing references', async () => {
|
||||
kfetchMock.mockResolvedValueOnce({
|
||||
httpMock.post.mockResolvedValueOnce({
|
||||
success: true,
|
||||
successCount: 2,
|
||||
});
|
||||
getConflictResolutions.mockResolvedValueOnce({});
|
||||
const result = await resolveImportErrors({
|
||||
http: httpMock,
|
||||
getConflictResolutions,
|
||||
state: {
|
||||
importCount: 0,
|
||||
|
@ -203,7 +210,7 @@ Object {
|
|||
"status": "success",
|
||||
}
|
||||
`);
|
||||
const formData = getFormData(kfetchMock.mock.calls[0][0].body);
|
||||
const formData = getFormData(extractBodyFromCall(0));
|
||||
expect(formData).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"file": "undefined",
|
||||
|
@ -232,6 +239,7 @@ Object {
|
|||
test(`doesn't resolve missing references if newIndexPatternId isn't defined`, async () => {
|
||||
getConflictResolutions.mockResolvedValueOnce({});
|
||||
const result = await resolveImportErrors({
|
||||
http: httpMock,
|
||||
getConflictResolutions,
|
||||
state: {
|
||||
importCount: 0,
|
||||
|
@ -276,7 +284,7 @@ Object {
|
|||
});
|
||||
|
||||
test('handles missing references then conflicts on the same errored objects', async () => {
|
||||
kfetchMock.mockResolvedValueOnce({
|
||||
httpMock.post.mockResolvedValueOnce({
|
||||
success: false,
|
||||
successCount: 0,
|
||||
errors: [
|
||||
|
@ -289,7 +297,7 @@ Object {
|
|||
},
|
||||
],
|
||||
});
|
||||
kfetchMock.mockResolvedValueOnce({
|
||||
httpMock.post.mockResolvedValueOnce({
|
||||
success: true,
|
||||
successCount: 1,
|
||||
});
|
||||
|
@ -298,6 +306,7 @@ Object {
|
|||
'a:1': true,
|
||||
});
|
||||
const result = await resolveImportErrors({
|
||||
http: httpMock,
|
||||
getConflictResolutions,
|
||||
state: {
|
||||
importCount: 0,
|
||||
|
@ -334,7 +343,7 @@ Object {
|
|||
"status": "success",
|
||||
}
|
||||
`);
|
||||
const formData1 = getFormData(kfetchMock.mock.calls[0][0].body);
|
||||
const formData1 = getFormData(extractBodyFromCall(0));
|
||||
expect(formData1).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"file": "undefined",
|
||||
|
@ -354,7 +363,7 @@ Object {
|
|||
],
|
||||
}
|
||||
`);
|
||||
const formData2 = getFormData(kfetchMock.mock.calls[1][0].body);
|
||||
const formData2 = getFormData(extractBodyFromCall(1));
|
||||
expect(formData2).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"file": "undefined",
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { FailedImport } from './process_import_response';
|
||||
|
||||
interface RetryObject {
|
||||
|
@ -27,13 +27,11 @@ interface RetryObject {
|
|||
replaceReferences?: any[];
|
||||
}
|
||||
|
||||
async function callResolveImportErrorsApi(file: File, retries: any) {
|
||||
async function callResolveImportErrorsApi(http: HttpStart, file: File, retries: any) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('retries', JSON.stringify(retries));
|
||||
return await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/saved_objects/_resolve_import_errors',
|
||||
return http.post<any>('/api/saved_objects/_resolve_import_errors', {
|
||||
headers: {
|
||||
// Important to be undefined, it forces proper headers to be set for FormData
|
||||
'Content-Type': undefined,
|
||||
|
@ -100,9 +98,11 @@ function mapImportFailureToRetryObject({
|
|||
}
|
||||
|
||||
export async function resolveImportErrors({
|
||||
http,
|
||||
getConflictResolutions,
|
||||
state,
|
||||
}: {
|
||||
http: HttpStart;
|
||||
getConflictResolutions: (objects: any[]) => Promise<Record<string, boolean>>;
|
||||
state: { importCount: number; failedImports?: FailedImport[] } & Record<string, any>;
|
||||
}) {
|
||||
|
@ -170,7 +170,7 @@ export async function resolveImportErrors({
|
|||
}
|
||||
|
||||
// Call API
|
||||
const response = await callResolveImportErrorsApi(file, retries);
|
||||
const response = await callResolveImportErrorsApi(http, file, retries);
|
||||
successImportCount += response.successCount;
|
||||
importFailures = [];
|
||||
for (const { error, ...obj } of response.errors || []) {
|
|
@ -23,11 +23,8 @@ import {
|
|||
saveObjects,
|
||||
saveObject,
|
||||
} from './resolve_saved_objects';
|
||||
import {
|
||||
SavedObject,
|
||||
SavedObjectLoader,
|
||||
} from '../../../../../../../../plugins/saved_objects/public';
|
||||
import { IndexPatternsContract } from '../../../../../../../../plugins/data/public';
|
||||
import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public';
|
||||
import { IndexPatternsContract } from '../../../data/public';
|
||||
|
||||
class SavedObjectNotFound extends Error {
|
||||
constructor(options: Record<string, any>) {
|
|
@ -20,15 +20,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { OverlayStart, SavedObjectReference } from 'src/core/public';
|
||||
import {
|
||||
SavedObject,
|
||||
SavedObjectLoader,
|
||||
} from '../../../../../../../../plugins/saved_objects/public';
|
||||
import {
|
||||
IndexPatternsContract,
|
||||
IIndexPattern,
|
||||
createSearchSource,
|
||||
} from '../../../../../../../../plugins/data/public';
|
||||
import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public';
|
||||
import { IndexPatternsContract, IIndexPattern, createSearchSource } from '../../../data/public';
|
||||
|
||||
type SavedObjectsRawDoc = Record<string, any>;
|
||||
|
||||
|
@ -55,7 +48,7 @@ function addJsonFieldToIndexPattern(
|
|||
target[fieldName] = JSON.parse(sourceString);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
i18n.translate('kbn.management.objects.parsingFieldErrorMessage', {
|
||||
i18n.translate('savedObjectsManagement.parsingFieldErrorMessage', {
|
||||
defaultMessage:
|
||||
'Error encountered parsing {fieldName} for index pattern {indexName}: {errorMessage}',
|
||||
values: {
|
||||
|
@ -103,18 +96,21 @@ async function importIndexPattern(
|
|||
if (!newId) {
|
||||
// We can override and we want to prompt for confirmation
|
||||
const isConfirmed = await openConfirm(
|
||||
i18n.translate('kbn.management.indexPattern.confirmOverwriteLabel', {
|
||||
i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', {
|
||||
values: { title },
|
||||
defaultMessage: "Are you sure you want to overwrite '{title}'?",
|
||||
}),
|
||||
{
|
||||
title: i18n.translate('kbn.management.indexPattern.confirmOverwriteTitle', {
|
||||
title: i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteTitle', {
|
||||
defaultMessage: 'Overwrite {type}?',
|
||||
values: { type },
|
||||
}),
|
||||
confirmButtonText: i18n.translate('kbn.management.indexPattern.confirmOverwriteButton', {
|
||||
defaultMessage: 'Overwrite',
|
||||
}),
|
||||
confirmButtonText: i18n.translate(
|
||||
'savedObjectsManagement.indexPattern.confirmOverwriteButton',
|
||||
{
|
||||
defaultMessage: 'Overwrite',
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Relationships } from './relationships';
|
||||
export { mountManagementSection } from './mount_section';
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* 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 React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HashRouter, Switch, Route, useParams, useLocation } from 'react-router-dom';
|
||||
import { parse } from 'query-string';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { CoreSetup, CoreStart, ChromeBreadcrumb, Capabilities } from 'src/core/public';
|
||||
import { ManagementAppMountParams } from '../../../management/public';
|
||||
import { DataPublicPluginStart } from '../../../data/public';
|
||||
import { StartDependencies, SavedObjectsManagementPluginStart } from '../plugin';
|
||||
import {
|
||||
ISavedObjectsManagementServiceRegistry,
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
} from '../services';
|
||||
import { SavedObjectsTable } from './objects_table';
|
||||
import { SavedObjectEdition } from './object_view';
|
||||
import { getAllowedTypes } from './../lib';
|
||||
|
||||
interface MountParams {
|
||||
core: CoreSetup<StartDependencies, SavedObjectsManagementPluginStart>;
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
mountParams: ManagementAppMountParams;
|
||||
}
|
||||
|
||||
let allowedObjectTypes: string[] | undefined;
|
||||
|
||||
export const mountManagementSection = async ({
|
||||
core,
|
||||
mountParams,
|
||||
serviceRegistry,
|
||||
}: MountParams) => {
|
||||
const [coreStart, { data }, pluginStart] = await core.getStartServices();
|
||||
const { element, basePath, setBreadcrumbs } = mountParams;
|
||||
if (allowedObjectTypes === undefined) {
|
||||
allowedObjectTypes = await getAllowedTypes(coreStart.http);
|
||||
}
|
||||
|
||||
const capabilities = coreStart.application.capabilities;
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<HashRouter basename={basePath}>
|
||||
<Switch>
|
||||
<Route path={'/:service/:id'} exact={true}>
|
||||
<RedirectToHomeIfUnauthorized capabilities={capabilities}>
|
||||
<SavedObjectsEditionPage
|
||||
coreStart={coreStart}
|
||||
serviceRegistry={serviceRegistry}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
/>
|
||||
</RedirectToHomeIfUnauthorized>
|
||||
</Route>
|
||||
<Route path={'/'} exact={false}>
|
||||
<RedirectToHomeIfUnauthorized capabilities={capabilities}>
|
||||
<SavedObjectsTablePage
|
||||
coreStart={coreStart}
|
||||
dataStart={data}
|
||||
serviceRegistry={serviceRegistry}
|
||||
actionRegistry={pluginStart.actions}
|
||||
allowedTypes={allowedObjectTypes}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
/>
|
||||
</RedirectToHomeIfUnauthorized>
|
||||
</Route>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</I18nProvider>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
};
|
||||
|
||||
const RedirectToHomeIfUnauthorized: React.FunctionComponent<{
|
||||
capabilities: Capabilities;
|
||||
}> = ({ children, capabilities }) => {
|
||||
const allowed = capabilities?.management?.kibana?.objects ?? false;
|
||||
if (!allowed) {
|
||||
window.location.hash = '/home';
|
||||
return null;
|
||||
}
|
||||
return children! as React.ReactElement;
|
||||
};
|
||||
|
||||
const SavedObjectsEditionPage = ({
|
||||
coreStart,
|
||||
serviceRegistry,
|
||||
setBreadcrumbs,
|
||||
}: {
|
||||
coreStart: CoreStart;
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
|
||||
}) => {
|
||||
const { service: serviceName, id } = useParams<{ service: string; id: string }>();
|
||||
const capabilities = coreStart.application.capabilities;
|
||||
|
||||
const { search } = useLocation();
|
||||
const query = parse(search);
|
||||
const service = serviceRegistry.get(serviceName);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('savedObjectsManagement.breadcrumb.index', {
|
||||
defaultMessage: 'Saved objects',
|
||||
}),
|
||||
href: '#/management/kibana/objects',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('savedObjectsManagement.breadcrumb.edit', {
|
||||
defaultMessage: 'Edit {savedObjectType}',
|
||||
values: { savedObjectType: service?.service.type ?? 'object' },
|
||||
}),
|
||||
},
|
||||
]);
|
||||
}, [setBreadcrumbs, service]);
|
||||
|
||||
return (
|
||||
<SavedObjectEdition
|
||||
id={id}
|
||||
serviceName={serviceName}
|
||||
serviceRegistry={serviceRegistry}
|
||||
savedObjectsClient={coreStart.savedObjects.client}
|
||||
overlays={coreStart.overlays}
|
||||
notifications={coreStart.notifications}
|
||||
capabilities={capabilities}
|
||||
notFoundType={query.notFound as string}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SavedObjectsTablePage = ({
|
||||
coreStart,
|
||||
dataStart,
|
||||
allowedTypes,
|
||||
serviceRegistry,
|
||||
actionRegistry,
|
||||
setBreadcrumbs,
|
||||
}: {
|
||||
coreStart: CoreStart;
|
||||
dataStart: DataPublicPluginStart;
|
||||
allowedTypes: string[];
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
actionRegistry: SavedObjectsManagementActionServiceStart;
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
|
||||
}) => {
|
||||
const capabilities = coreStart.application.capabilities;
|
||||
const itemsPerPage = coreStart.uiSettings.get<number>('savedObjects:perPage', 50);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('savedObjectsManagement.breadcrumb.index', {
|
||||
defaultMessage: 'Saved objects',
|
||||
}),
|
||||
href: '#/management/kibana/objects',
|
||||
},
|
||||
]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
return (
|
||||
<SavedObjectsTable
|
||||
allowedTypes={allowedTypes}
|
||||
serviceRegistry={serviceRegistry}
|
||||
actionRegistry={actionRegistry}
|
||||
savedObjectsClient={coreStart.savedObjects.client}
|
||||
indexPatterns={dataStart.indexPatterns}
|
||||
http={coreStart.http}
|
||||
overlays={coreStart.overlays}
|
||||
notifications={coreStart.notifications}
|
||||
applications={coreStart.application}
|
||||
perPageConfig={itemsPerPage}
|
||||
goInspectObject={savedObject => {
|
||||
const { editUrl } = savedObject.meta;
|
||||
if (editUrl) {
|
||||
// previously, kbnUrl.change(object.meta.editUrl); was used.
|
||||
// using direct access to location.hash seems the only option for now,
|
||||
// as using react-router-dom will prefix the url with the router's basename
|
||||
// which should be ignored there.
|
||||
window.location.hash = editUrl;
|
||||
}
|
||||
}}
|
||||
canGoInApp={savedObject => {
|
||||
const { inAppUrl } = savedObject.meta;
|
||||
return inAppUrl ? get(capabilities, inAppUrl.uiCapabilitiesPath) : false;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -23,7 +23,7 @@ exports[`Intro component renders correctly 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit {title}"
|
||||
id="kbn.management.objects.view.editItemTitle"
|
||||
id="savedObjectsManagement.view.editItemTitle"
|
||||
values={
|
||||
Object {
|
||||
"title": "search",
|
||||
|
@ -85,7 +85,7 @@ exports[`Intro component renders correctly 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="View {title}"
|
||||
id="kbn.management.objects.view.viewItemButtonLabel"
|
||||
id="savedObjectsManagement.view.viewItemButtonLabel"
|
||||
values={
|
||||
Object {
|
||||
"title": "search",
|
||||
|
@ -140,7 +140,7 @@ exports[`Intro component renders correctly 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete {title}"
|
||||
id="kbn.management.objects.view.deleteItemButtonLabel"
|
||||
id="savedObjectsManagement.view.deleteItemButtonLabel"
|
||||
values={
|
||||
Object {
|
||||
"title": "search",
|
|
@ -8,7 +8,7 @@ exports[`Intro component renders correctly 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Proceed with caution!"
|
||||
id="kbn.management.objects.view.howToModifyObjectTitle"
|
||||
id="savedObjectsManagement.view.howToModifyObjectTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ exports[`Intro component renders correctly 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Proceed with caution!"
|
||||
id="kbn.management.objects.view.howToModifyObjectTitle"
|
||||
id="savedObjectsManagement.view.howToModifyObjectTitle"
|
||||
values={Object {}}
|
||||
>
|
||||
Proceed with caution!
|
||||
|
@ -53,7 +53,7 @@ exports[`Intro component renders correctly 1`] = `
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be."
|
||||
id="kbn.management.objects.view.howToModifyObjectDescription"
|
||||
id="savedObjectsManagement.view.howToModifyObjectDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be.
|
|
@ -10,7 +10,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
There is a problem with this saved object
|
||||
|
@ -55,7 +55,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="The index pattern associated with this object no longer exists."
|
||||
id="kbn.management.objects.view.indexPatternDoesNotExistErrorMessage"
|
||||
id="savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
The index pattern associated with this object no longer exists.
|
||||
|
@ -64,7 +64,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
|
||||
id="kbn.management.objects.view.howToFixErrorDescription"
|
||||
id="savedObjectsManagement.view.howToFixErrorDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
|
||||
|
@ -87,7 +87,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
There is a problem with this saved object
|
||||
|
@ -132,7 +132,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="A field associated with this object no longer exists in the index pattern."
|
||||
id="kbn.management.objects.view.fieldDoesNotExistErrorMessage"
|
||||
id="savedObjectsManagement.view.fieldDoesNotExistErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
A field associated with this object no longer exists in the index pattern.
|
||||
|
@ -141,7 +141,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
|
||||
id="kbn.management.objects.view.howToFixErrorDescription"
|
||||
id="savedObjectsManagement.view.howToFixErrorDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
|
||||
|
@ -164,7 +164,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
There is a problem with this saved object
|
||||
|
@ -209,7 +209,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="The saved search associated with this object no longer exists."
|
||||
id="kbn.management.objects.view.savedSearchDoesNotExistErrorMessage"
|
||||
id="savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
The saved search associated with this object no longer exists.
|
||||
|
@ -218,7 +218,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
|
||||
id="kbn.management.objects.view.howToFixErrorDescription"
|
||||
id="savedObjectsManagement.view.howToFixErrorDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
|
||||
|
@ -241,7 +241,7 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
values={Object {}}
|
||||
>
|
||||
There is a problem with this saved object
|
||||
|
@ -287,7 +287,7 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
|
|||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
|
||||
id="kbn.management.objects.view.howToFixErrorDescription"
|
||||
id="savedObjectsManagement.view.howToFixErrorDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
|
|
@ -104,9 +104,9 @@ export class Field extends PureComponent<FieldProps> {
|
|||
id={this.fieldId}
|
||||
label={
|
||||
!!currentValue ? (
|
||||
<FormattedMessage id="kbn.management.objects.field.onLabel" defaultMessage="On" />
|
||||
<FormattedMessage id="savedObjectsManagement.field.onLabel" defaultMessage="On" />
|
||||
) : (
|
||||
<FormattedMessage id="kbn.management.objects.field.offLabel" defaultMessage="Off" />
|
||||
<FormattedMessage id="savedObjectsManagement.field.offLabel" defaultMessage="Off" />
|
||||
)
|
||||
}
|
||||
checked={!!currentValue}
|
|
@ -29,15 +29,11 @@ import {
|
|||
import { cloneDeep, set } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
SimpleSavedObject,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../../../../../../core/public';
|
||||
|
||||
import { SavedObjectLoader } from '../../../../../../../../../plugins/saved_objects/public';
|
||||
import { SimpleSavedObject, SavedObjectsClientContract } from '../../../../../../core/public';
|
||||
import { SavedObjectLoader } from '../../../../../saved_objects/public';
|
||||
import { Field } from './field';
|
||||
import { ObjectField, FieldState, SubmittedFormData } from '../../types';
|
||||
import { createFieldList } from '../../lib/create_field_list';
|
||||
import { createFieldList } from '../../../lib';
|
||||
|
||||
interface FormProps {
|
||||
object: SimpleSavedObject;
|
||||
|
@ -96,7 +92,7 @@ export class Form extends Component<FormProps, FormState> {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill={true}
|
||||
aria-label={i18n.translate('kbn.management.objects.view.saveButtonAriaLabel', {
|
||||
aria-label={i18n.translate('savedObjectsManagement.view.saveButtonAriaLabel', {
|
||||
defaultMessage: 'Save { title } object',
|
||||
values: {
|
||||
title: service.type,
|
||||
|
@ -107,7 +103,7 @@ export class Form extends Component<FormProps, FormState> {
|
|||
data-test-subj="savedObjectEditSave"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.saveButtonLabel"
|
||||
id="savedObjectsManagement.view.saveButtonLabel"
|
||||
defaultMessage="Save { title } object"
|
||||
values={{ title: service.type }}
|
||||
/>
|
||||
|
@ -117,14 +113,14 @@ export class Form extends Component<FormProps, FormState> {
|
|||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
aria-label={i18n.translate('kbn.management.objects.view.cancelButtonAriaLabel', {
|
||||
aria-label={i18n.translate('savedObjectsManagement.view.cancelButtonAriaLabel', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
onClick={this.onCancel}
|
||||
data-test-subj="savedObjectEditCancel"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.cancelButtonLabel"
|
||||
id="savedObjectsManagement.view.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
|
@ -52,7 +52,7 @@ export const Header = ({
|
|||
{canEdit ? (
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.editItemTitle"
|
||||
id="savedObjectsManagement.view.editItemTitle"
|
||||
defaultMessage="Edit {title}"
|
||||
values={{ title: type }}
|
||||
/>
|
||||
|
@ -60,7 +60,7 @@ export const Header = ({
|
|||
) : (
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.viewItemTitle"
|
||||
id="savedObjectsManagement.view.viewItemTitle"
|
||||
defaultMessage="View {title}"
|
||||
values={{ title: type }}
|
||||
/>
|
||||
|
@ -79,7 +79,7 @@ export const Header = ({
|
|||
data-test-subj="savedObjectEditViewInApp"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.viewItemButtonLabel"
|
||||
id="savedObjectsManagement.view.viewItemButtonLabel"
|
||||
defaultMessage="View {title}"
|
||||
values={{ title: type }}
|
||||
/>
|
||||
|
@ -96,7 +96,7 @@ export const Header = ({
|
|||
data-test-subj="savedObjectEditDelete"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.deleteItemButtonLabel"
|
||||
id="savedObjectsManagement.view.deleteItemButtonLabel"
|
||||
defaultMessage="Delete {title}"
|
||||
values={{ title: type }}
|
||||
/>
|
|
@ -26,7 +26,7 @@ export const Intro = () => {
|
|||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.howToModifyObjectTitle"
|
||||
id="savedObjectsManagement.view.howToModifyObjectTitle"
|
||||
defaultMessage="Proceed with caution!"
|
||||
/>
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export const Intro = () => {
|
|||
>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.howToModifyObjectDescription"
|
||||
id="savedObjectsManagement.view.howToModifyObjectDescription"
|
||||
defaultMessage="Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be."
|
||||
/>
|
||||
</div>
|
|
@ -31,21 +31,21 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
|
|||
case 'search':
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.savedSearchDoesNotExistErrorMessage"
|
||||
id="savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage"
|
||||
defaultMessage="The saved search associated with this object no longer exists."
|
||||
/>
|
||||
);
|
||||
case 'index-pattern':
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.indexPatternDoesNotExistErrorMessage"
|
||||
id="savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage"
|
||||
defaultMessage="The index pattern associated with this object no longer exists."
|
||||
/>
|
||||
);
|
||||
case 'index-pattern-field':
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.fieldDoesNotExistErrorMessage"
|
||||
id="savedObjectsManagement.view.fieldDoesNotExistErrorMessage"
|
||||
defaultMessage="A field associated with this object no longer exists in the index pattern."
|
||||
/>
|
||||
);
|
||||
|
@ -58,7 +58,7 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
|
|||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.savedObjectProblemErrorMessage"
|
||||
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
|
||||
defaultMessage="There is a problem with this saved object"
|
||||
/>
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
|
|||
<div>{getMessage()}</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.view.howToFixErrorDescription"
|
||||
id="savedObjectsManagement.view.howToFixErrorDescription"
|
||||
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
|
||||
/>
|
||||
</div>
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Flyout } from './flyout';
|
||||
export { SavedObjectEdition } from './saved_object_view';
|
|
@ -26,16 +26,16 @@ import {
|
|||
OverlayStart,
|
||||
NotificationsStart,
|
||||
SimpleSavedObject,
|
||||
} from '../../../../../../../core/public';
|
||||
import { ISavedObjectsManagementRegistry } from '../../saved_object_registry';
|
||||
import { Header, NotFoundErrors, Intro, Form } from './components/object_view';
|
||||
import { canViewInApp } from './lib/in_app_url';
|
||||
import { SubmittedFormData } from './types';
|
||||
} from '../../../../../core/public';
|
||||
import { ISavedObjectsManagementServiceRegistry } from '../../services';
|
||||
import { Header, NotFoundErrors, Intro, Form } from './components';
|
||||
import { canViewInApp } from '../../lib';
|
||||
import { SubmittedFormData } from '../types';
|
||||
|
||||
interface SavedObjectEditionProps {
|
||||
id: string;
|
||||
serviceName: string;
|
||||
serviceRegistry: ISavedObjectsManagementRegistry;
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
capabilities: Capabilities;
|
||||
overlays: OverlayStart;
|
||||
notifications: NotificationsStart;
|
||||
|
@ -135,17 +135,17 @@ export class SavedObjectEdition extends Component<
|
|||
const { type, object } = this.state;
|
||||
|
||||
const confirmed = await overlays.openConfirm(
|
||||
i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', {
|
||||
i18n.translate('savedObjectsManagement.deleteConfirm.modalDescription', {
|
||||
defaultMessage: 'This action permanently removes the object from Kibana.',
|
||||
}),
|
||||
{
|
||||
confirmButtonText: i18n.translate(
|
||||
'kbn.management.objects.confirmModalOptions.deleteButtonLabel',
|
||||
'savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
),
|
||||
title: i18n.translate('kbn.management.objects.confirmModalOptions.modalTitle', {
|
||||
title: i18n.translate('savedObjectsManagement.deleteConfirm.modalTitle', {
|
||||
defaultMessage: `Delete '{title}'?`,
|
||||
values: {
|
||||
title: object?.attributes?.title || 'saved Kibana object',
|
|
@ -1,19 +1,19 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ObjectsTable delete should show a confirm modal 1`] = `
|
||||
exports[`SavedObjectsTable delete should show a confirm modal 1`] = `
|
||||
<EuiConfirmModal
|
||||
buttonColor="danger"
|
||||
cancelButtonText={
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
confirmButtonText={
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete"
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete saved objects"
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModalTitle"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModalTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="This action will delete the following saved objects:"
|
||||
id="kbn.management.objects.deleteSavedObjectsConfirmModalDescription"
|
||||
id="savedObjectsManagement.deleteSavedObjectsConfirmModalDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
|
@ -58,12 +58,10 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"title": "Title 1",
|
||||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "3",
|
||||
"title": "Title 2",
|
||||
"type": "dashboard",
|
||||
},
|
||||
]
|
||||
|
@ -76,7 +74,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `
|
|||
</EuiConfirmModal>
|
||||
`;
|
||||
|
||||
exports[`ObjectsTable export should allow the user to choose when exporting all 1`] = `
|
||||
exports[`SavedObjectsTable export should allow the user to choose when exporting all 1`] = `
|
||||
<EuiModal
|
||||
onClose={[Function]}
|
||||
>
|
||||
|
@ -84,7 +82,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all
|
|||
<EuiModalHeaderTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export {filteredItemCount, plural, one{# object} other {# objects}}"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModalTitle"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModalTitle"
|
||||
values={
|
||||
Object {
|
||||
"filteredItemCount": 4,
|
||||
|
@ -103,7 +101,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Select which types to export"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModalDescription"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModalDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -149,7 +147,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Include related objects"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -173,7 +171,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -187,7 +185,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export all"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -199,23 +197,87 @@ exports[`ObjectsTable export should allow the user to choose when exporting all
|
|||
</EuiModal>
|
||||
`;
|
||||
|
||||
exports[`ObjectsTable import should show the flyout 1`] = `
|
||||
exports[`SavedObjectsTable import should show the flyout 1`] = `
|
||||
<Flyout
|
||||
allowedTypes={
|
||||
Array [
|
||||
"index-pattern",
|
||||
"visualization",
|
||||
"dashboard",
|
||||
"search",
|
||||
]
|
||||
}
|
||||
close={[Function]}
|
||||
confirmModalPromise={[MockFunction]}
|
||||
done={[Function]}
|
||||
http={
|
||||
Object {
|
||||
"addLoadingCountSource": [MockFunction],
|
||||
"anonymousPaths": Object {
|
||||
"isAnonymous": [MockFunction],
|
||||
"register": [MockFunction],
|
||||
},
|
||||
"basePath": BasePath {
|
||||
"basePath": "",
|
||||
"get": [Function],
|
||||
"prepend": [Function],
|
||||
"remove": [Function],
|
||||
"serverBasePath": "",
|
||||
},
|
||||
"delete": [MockFunction],
|
||||
"fetch": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"getLoadingCount$": [MockFunction],
|
||||
"head": [MockFunction],
|
||||
"intercept": [MockFunction],
|
||||
"options": [MockFunction],
|
||||
"patch": [MockFunction],
|
||||
"post": [MockFunction],
|
||||
"put": [MockFunction],
|
||||
}
|
||||
}
|
||||
indexPatterns={
|
||||
Object {
|
||||
"clearCache": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"make": [Function],
|
||||
}
|
||||
}
|
||||
overlays={
|
||||
Object {
|
||||
"banners": Object {
|
||||
"add": [MockFunction],
|
||||
"get$": [MockFunction],
|
||||
"getComponent": [MockFunction],
|
||||
"remove": [MockFunction],
|
||||
"replace": [MockFunction],
|
||||
},
|
||||
"openConfirm": [MockFunction],
|
||||
"openFlyout": [MockFunction],
|
||||
"openModal": [MockFunction],
|
||||
}
|
||||
}
|
||||
serviceRegistry={
|
||||
Object {
|
||||
"all": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"register": [MockFunction],
|
||||
}
|
||||
}
|
||||
newIndexPatternUrl=""
|
||||
services={Array []}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`ObjectsTable relationships should show the flyout 1`] = `
|
||||
exports[`SavedObjectsTable relationships should show the flyout 1`] = `
|
||||
<Relationships
|
||||
basePath={
|
||||
BasePath {
|
||||
"basePath": "",
|
||||
"get": [Function],
|
||||
"prepend": [Function],
|
||||
"remove": [Function],
|
||||
"serverBasePath": "",
|
||||
}
|
||||
}
|
||||
canGoInApp={[Function]}
|
||||
close={[Function]}
|
||||
getRelationships={[Function]}
|
||||
goInspectObject={[Function]}
|
||||
|
@ -237,7 +299,7 @@ exports[`ObjectsTable relationships should show the flyout 1`] = `
|
|||
/>
|
||||
`;
|
||||
|
||||
exports[`ObjectsTable should render normally 1`] = `
|
||||
exports[`SavedObjectsTable should render normally 1`] = `
|
||||
<EuiPageContent
|
||||
horizontalPosition="center"
|
||||
>
|
||||
|
@ -251,7 +313,23 @@ exports[`ObjectsTable should render normally 1`] = `
|
|||
size="xs"
|
||||
/>
|
||||
<Table
|
||||
actionRegistry={
|
||||
Object {
|
||||
"getAll": [MockFunction],
|
||||
"has": [MockFunction],
|
||||
}
|
||||
}
|
||||
basePath={
|
||||
BasePath {
|
||||
"basePath": "",
|
||||
"get": [Function],
|
||||
"prepend": [Function],
|
||||
"remove": [Function],
|
||||
"serverBasePath": "",
|
||||
}
|
||||
}
|
||||
canDelete={false}
|
||||
canGoInApp={[Function]}
|
||||
filterOptions={
|
||||
Array [
|
||||
Object {
|
|
@ -18,7 +18,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
|
|||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Import saved objects"
|
||||
id="kbn.management.objects.objectsTable.flyout.importSavedObjectTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSavedObjectTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
|
@ -36,7 +36,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Index Pattern Conflicts"
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The following saved objects use index patterns that do not exist. Please select the index patterns you'd like re-associated with them. You can {indexPatternLink} if necessary."
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
values={
|
||||
Object {
|
||||
"indexPatternLink": <ForwardRef
|
||||
|
@ -52,7 +52,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="create a new index pattern"
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
|
@ -131,7 +131,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="kbn.management.objects.objectsTable.flyout.import.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.import.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -148,7 +148,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Confirm all changes"
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -164,6 +164,30 @@ exports[`Flyout conflicts should allow conflict resolution 2`] = `
|
|||
Array [
|
||||
Object {
|
||||
"getConflictResolutions": [Function],
|
||||
"http": Object {
|
||||
"addLoadingCountSource": [MockFunction],
|
||||
"anonymousPaths": Object {
|
||||
"isAnonymous": [MockFunction],
|
||||
"register": [MockFunction],
|
||||
},
|
||||
"basePath": BasePath {
|
||||
"basePath": "",
|
||||
"get": [Function],
|
||||
"prepend": [Function],
|
||||
"remove": [Function],
|
||||
"serverBasePath": "",
|
||||
},
|
||||
"delete": [MockFunction],
|
||||
"fetch": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"getLoadingCount$": [MockFunction],
|
||||
"head": [MockFunction],
|
||||
"intercept": [MockFunction],
|
||||
"options": [MockFunction],
|
||||
"patch": [MockFunction],
|
||||
"post": [MockFunction],
|
||||
"put": [MockFunction],
|
||||
},
|
||||
"state": Object {
|
||||
"conflictedIndexPatterns": undefined,
|
||||
"conflictedSavedObjectsLinkedToSavedSearches": undefined,
|
||||
|
@ -243,7 +267,7 @@ exports[`Flyout conflicts should handle errors 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Import failed"
|
||||
id="kbn.management.objects.objectsTable.flyout.importFailedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importFailedTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -251,7 +275,7 @@ exports[`Flyout conflicts should handle errors 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Failed to import {failedImportCount} of {totalImportCount} objects. Import failed"
|
||||
id="kbn.management.objects.objectsTable.flyout.importFailedDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importFailedDescription"
|
||||
values={
|
||||
Object {
|
||||
"failedImportCount": 1,
|
||||
|
@ -272,7 +296,7 @@ exports[`Flyout errors should display unsupported type errors properly 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Import failed"
|
||||
id="kbn.management.objects.objectsTable.flyout.importFailedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importFailedTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -280,7 +304,7 @@ exports[`Flyout errors should display unsupported type errors properly 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Failed to import {failedImportCount} of {totalImportCount} objects. Import failed"
|
||||
id="kbn.management.objects.objectsTable.flyout.importFailedDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importFailedDescription"
|
||||
values={
|
||||
Object {
|
||||
"failedImportCount": 1,
|
||||
|
@ -313,7 +337,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Import saved objects"
|
||||
id="kbn.management.objects.objectsTable.flyout.importSavedObjectTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSavedObjectTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
|
@ -331,7 +355,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Support for JSON files is going away"
|
||||
id="kbn.management.objects.objectsTable.flyout.legacyFileUsedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.legacyFileUsedTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -339,7 +363,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Use our updated export to generate NDJSON files, and you'll be all set."
|
||||
id="kbn.management.objects.objectsTable.flyout.legacyFileUsedBody"
|
||||
id="savedObjectsManagement.objectsTable.flyout.legacyFileUsedBody"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
|
@ -356,7 +380,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Index Pattern Conflicts"
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -364,7 +388,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The following saved objects use index patterns that do not exist. Please select the index patterns you'd like re-associated with them. You can {indexPatternLink} if necessary."
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
values={
|
||||
Object {
|
||||
"indexPatternLink": <ForwardRef
|
||||
|
@ -372,7 +396,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="create a new index pattern"
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
|
@ -462,7 +486,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="kbn.management.objects.objectsTable.flyout.import.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.import.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -479,7 +503,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Confirm all changes"
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -498,7 +522,7 @@ Array [
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Support for JSON files is going away"
|
||||
id="kbn.management.objects.objectsTable.flyout.legacyFileUsedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.legacyFileUsedTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -506,7 +530,7 @@ Array [
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Use our updated export to generate NDJSON files, and you'll be all set."
|
||||
id="kbn.management.objects.objectsTable.flyout.legacyFileUsedBody"
|
||||
id="savedObjectsManagement.objectsTable.flyout.legacyFileUsedBody"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
|
@ -518,7 +542,7 @@ Array [
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Index Pattern Conflicts"
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -526,7 +550,7 @@ Array [
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The following saved objects use index patterns that do not exist. Please select the index patterns you'd like re-associated with them. You can {indexPatternLink} if necessary."
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
values={
|
||||
Object {
|
||||
"indexPatternLink": <ForwardRef
|
||||
|
@ -534,7 +558,7 @@ Array [
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="create a new index pattern"
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
|
@ -548,7 +572,7 @@ Array [
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Sorry, there was an error"
|
||||
id="kbn.management.objects.objectsTable.flyout.errorCalloutTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.errorCalloutTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -578,7 +602,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Import saved objects"
|
||||
id="kbn.management.objects.objectsTable.flyout.importSavedObjectTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSavedObjectTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
|
@ -595,7 +619,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Please select a file to import"
|
||||
id="kbn.management.objects.objectsTable.flyout.selectFileToImportFormRowLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.selectFileToImportFormRowLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -607,7 +631,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
initialPromptText={
|
||||
<FormattedMessage
|
||||
defaultMessage="Import"
|
||||
id="kbn.management.objects.objectsTable.flyout.importPromptText"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importPromptText"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -628,7 +652,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Automatically overwrite all saved objects?"
|
||||
id="kbn.management.objects.objectsTable.flyout.overwriteSavedObjectsLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.overwriteSavedObjectsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -651,7 +675,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="kbn.management.objects.objectsTable.flyout.import.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.import.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -668,7 +692,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Import"
|
||||
id="kbn.management.objects.objectsTable.flyout.import.confirmButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.import.confirmButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
|
@ -13,7 +13,7 @@ exports[`Header should render normally 1`] = `
|
|||
<h1>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved Objects"
|
||||
id="kbn.management.objects.objectsTable.header.savedObjectsTitle"
|
||||
id="savedObjectsManagement.objectsTable.header.savedObjectsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h1>
|
||||
|
@ -38,7 +38,7 @@ exports[`Header should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export {filteredCount, plural, one{# object} other {# objects}}"
|
||||
id="kbn.management.objects.objectsTable.header.exportButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.header.exportButtonLabel"
|
||||
values={
|
||||
Object {
|
||||
"filteredCount": 2,
|
||||
|
@ -58,7 +58,7 @@ exports[`Header should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Import"
|
||||
id="kbn.management.objects.objectsTable.header.importButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.header.importButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -73,7 +73,7 @@ exports[`Header should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Refresh"
|
||||
id="kbn.management.objects.objectsTable.header.refreshButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.header.refreshButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -93,7 +93,7 @@ exports[`Header should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen."
|
||||
id="kbn.management.objects.objectsTable.howToDeleteSavedObjectsDescription"
|
||||
id="savedObjectsManagement.objectsTable.howToDeleteSavedObjectsDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiTextColor>
|
|
@ -202,7 +202,7 @@ exports[`Relationships should render errors 1`] = `
|
|||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Error"
|
||||
id="kbn.management.objects.objectsTable.relationships.renderErrorMessage"
|
||||
id="savedObjectsManagement.objectsTable.relationships.renderErrorMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
|
@ -36,7 +36,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete"
|
||||
id="kbn.management.objects.objectsTable.table.deleteButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.deleteButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>,
|
||||
|
@ -51,7 +51,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export"
|
||||
id="kbn.management.objects.objectsTable.table.exportPopoverButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.exportPopoverButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -72,7 +72,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Options"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.exportOptionsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Include related objects"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export"
|
||||
id="kbn.management.objects.objectsTable.table.exportButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.exportButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -171,6 +171,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
items={
|
||||
Array [
|
||||
Object {
|
||||
"attributes": Object {},
|
||||
"id": "1",
|
||||
"meta": Object {
|
||||
"editUrl": "#/management/kibana/index_patterns/1",
|
||||
|
@ -181,6 +182,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
|
|||
},
|
||||
"title": "MyIndexPattern*",
|
||||
},
|
||||
"references": Array [],
|
||||
"type": "index-pattern",
|
||||
},
|
||||
]
|
||||
|
@ -249,7 +251,7 @@ exports[`Table should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete"
|
||||
id="kbn.management.objects.objectsTable.table.deleteButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.deleteButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>,
|
||||
|
@ -264,7 +266,7 @@ exports[`Table should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export"
|
||||
id="kbn.management.objects.objectsTable.table.exportPopoverButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.exportPopoverButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -285,7 +287,7 @@ exports[`Table should render normally 1`] = `
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Options"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.exportOptionsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -296,7 +298,7 @@ exports[`Table should render normally 1`] = `
|
|||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Include related objects"
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
|
@ -319,7 +321,7 @@ exports[`Table should render normally 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export"
|
||||
id="kbn.management.objects.objectsTable.table.exportButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.exportButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -384,6 +386,7 @@ exports[`Table should render normally 1`] = `
|
|||
items={
|
||||
Array [
|
||||
Object {
|
||||
"attributes": Object {},
|
||||
"id": "1",
|
||||
"meta": Object {
|
||||
"editUrl": "#/management/kibana/index_patterns/1",
|
||||
|
@ -394,6 +397,7 @@ exports[`Table should render normally 1`] = `
|
|||
},
|
||||
"title": "MyIndexPattern*",
|
||||
},
|
||||
"references": Array [],
|
||||
"type": "index-pattern",
|
||||
},
|
||||
]
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 const importFileMock = jest.fn();
|
||||
jest.doMock('../../../lib/import_file', () => ({
|
||||
importFile: importFileMock,
|
||||
}));
|
||||
|
||||
export const resolveImportErrorsMock = jest.fn();
|
||||
jest.doMock('../../../lib/resolve_import_errors', () => ({
|
||||
resolveImportErrors: resolveImportErrorsMock,
|
||||
}));
|
||||
|
||||
export const importLegacyFileMock = jest.fn();
|
||||
jest.doMock('../../../lib/import_legacy_file', () => ({
|
||||
importLegacyFile: importLegacyFileMock,
|
||||
}));
|
||||
|
||||
export const resolveSavedObjectsMock = jest.fn();
|
||||
export const resolveSavedSearchesMock = jest.fn();
|
||||
export const resolveIndexPatternConflictsMock = jest.fn();
|
||||
export const saveObjectsMock = jest.fn();
|
||||
jest.doMock('../../../lib/resolve_saved_objects', () => ({
|
||||
resolveSavedObjects: resolveSavedObjectsMock,
|
||||
resolveSavedSearches: resolveSavedSearchesMock,
|
||||
resolveIndexPatternConflicts: resolveIndexPatternConflictsMock,
|
||||
saveObjects: saveObjectsMock,
|
||||
}));
|
|
@ -17,68 +17,62 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
importFileMock,
|
||||
importLegacyFileMock,
|
||||
resolveImportErrorsMock,
|
||||
resolveIndexPatternConflictsMock,
|
||||
resolveSavedObjectsMock,
|
||||
resolveSavedSearchesMock,
|
||||
saveObjectsMock,
|
||||
} from './flyout.test.mocks';
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers';
|
||||
import { mockManagementPlugin } from '../../../../../../../../../../../../plugins/index_pattern_management/public/mocks';
|
||||
import { Flyout } from '../flyout';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { serviceRegistryMock } from '../../../services/service_registry.mock';
|
||||
import { Flyout, FlyoutProps, FlyoutState } from './flyout';
|
||||
import { ShallowWrapper } from 'enzyme';
|
||||
|
||||
jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() }));
|
||||
|
||||
jest.mock('../../../../../lib/import_file', () => ({
|
||||
importFile: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../lib/resolve_import_errors', () => ({
|
||||
resolveImportErrors: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ui/chrome', () => ({
|
||||
addBasePath: () => {},
|
||||
getInjected: () => ['index-pattern', 'visualization', 'dashboard', 'search'],
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../lib/import_legacy_file', () => ({
|
||||
importLegacyFile: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../lib/resolve_saved_objects', () => ({
|
||||
resolveSavedObjects: jest.fn(),
|
||||
resolveSavedSearches: jest.fn(),
|
||||
resolveIndexPatternConflicts: jest.fn(),
|
||||
saveObjects: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../../../../../../../../plugins/index_pattern_management/public', () => ({
|
||||
setup: mockManagementPlugin.createSetupContract(),
|
||||
start: mockManagementPlugin.createStartContract(),
|
||||
}));
|
||||
|
||||
jest.mock('ui/notify', () => ({}));
|
||||
|
||||
const defaultProps = {
|
||||
close: jest.fn(),
|
||||
done: jest.fn(),
|
||||
services: [],
|
||||
newIndexPatternUrl: '',
|
||||
getConflictResolutions: jest.fn(),
|
||||
confirmModalPromise: jest.fn(),
|
||||
indexPatterns: {
|
||||
getFields: jest.fn().mockImplementation(() => [{ id: '1' }, { id: '2' }]),
|
||||
},
|
||||
};
|
||||
|
||||
const mockFile = {
|
||||
const mockFile = ({
|
||||
name: 'foo.ndjson',
|
||||
path: '/home/foo.ndjson',
|
||||
};
|
||||
const legacyMockFile = {
|
||||
} as unknown) as File;
|
||||
const legacyMockFile = ({
|
||||
name: 'foo.json',
|
||||
path: '/home/foo.json',
|
||||
};
|
||||
} as unknown) as File;
|
||||
|
||||
describe('Flyout', () => {
|
||||
let defaultProps: FlyoutProps;
|
||||
|
||||
const shallowRender = (props: FlyoutProps) => {
|
||||
return (shallowWithI18nProvider(<Flyout {...props} />) as unknown) as ShallowWrapper<
|
||||
FlyoutProps,
|
||||
FlyoutState,
|
||||
Flyout
|
||||
>;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const { http, overlays } = coreMock.createStart();
|
||||
|
||||
defaultProps = {
|
||||
close: jest.fn(),
|
||||
done: jest.fn(),
|
||||
newIndexPatternUrl: '',
|
||||
indexPatterns: {
|
||||
getFields: jest.fn().mockImplementation(() => [{ id: '1' }, { id: '2' }]),
|
||||
} as any,
|
||||
overlays,
|
||||
http,
|
||||
allowedTypes: ['search', 'index-pattern', 'visualization'],
|
||||
serviceRegistry: serviceRegistryMock.create(),
|
||||
};
|
||||
});
|
||||
|
||||
it('should render import step', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -89,7 +83,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should toggle the overwrite all control', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -102,7 +96,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should allow picking a file', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -115,7 +109,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should allow removing a file', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await Promise.resolve();
|
||||
|
@ -130,22 +124,21 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should handle invalid files', async () => {
|
||||
const { importLegacyFile } = require('../../../../../lib/import_legacy_file');
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
importLegacyFile.mockImplementation(() => {
|
||||
importLegacyFileMock.mockImplementation(() => {
|
||||
throw new Error('foobar');
|
||||
});
|
||||
|
||||
await component.instance().legacyImport();
|
||||
expect(component.state('error')).toBe('The file could not be processed.');
|
||||
|
||||
importLegacyFile.mockImplementation(() => ({
|
||||
importLegacyFileMock.mockImplementation(() => ({
|
||||
invalid: true,
|
||||
}));
|
||||
|
||||
|
@ -156,11 +149,8 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
describe('conflicts', () => {
|
||||
const { importFile } = require('../../../../../lib/import_file');
|
||||
const { resolveImportErrors } = require('../../../../../lib/resolve_import_errors');
|
||||
|
||||
beforeEach(() => {
|
||||
importFile.mockImplementation(() => ({
|
||||
importFileMock.mockImplementation(() => ({
|
||||
success: false,
|
||||
successCount: 0,
|
||||
errors: [
|
||||
|
@ -180,7 +170,7 @@ describe('Flyout', () => {
|
|||
},
|
||||
],
|
||||
}));
|
||||
resolveImportErrors.mockImplementation(() => ({
|
||||
resolveImportErrorsMock.mockImplementation(() => ({
|
||||
status: 'success',
|
||||
importCount: 1,
|
||||
failedImports: [],
|
||||
|
@ -188,7 +178,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should figure out unmatchedReferences', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -198,7 +188,7 @@ describe('Flyout', () => {
|
|||
component.setState({ file: mockFile, isLegacyFile: false });
|
||||
await component.instance().import();
|
||||
|
||||
expect(importFile).toHaveBeenCalledWith(mockFile, true);
|
||||
expect(importFileMock).toHaveBeenCalledWith(defaultProps.http, mockFile, true);
|
||||
expect(component.state()).toMatchObject({
|
||||
conflictedIndexPatterns: undefined,
|
||||
conflictedSavedObjectsLinkedToSavedSearches: undefined,
|
||||
|
@ -223,7 +213,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should allow conflict resolution', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -239,7 +229,7 @@ describe('Flyout', () => {
|
|||
|
||||
// Ensure we can change the resolution
|
||||
component.instance().onIndexChanged('MyIndexPattern*', { target: { value: '2' } });
|
||||
expect(component.state('unmatchedReferences')[0].newIndexPatternId).toBe('2');
|
||||
expect(component.state('unmatchedReferences')![0].newIndexPatternId).toBe('2');
|
||||
|
||||
// Let's resolve now
|
||||
await component
|
||||
|
@ -247,18 +237,18 @@ describe('Flyout', () => {
|
|||
.simulate('click');
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
expect(resolveImportErrors).toMatchSnapshot();
|
||||
expect(resolveImportErrorsMock).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle errors', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
resolveImportErrors.mockImplementation(() => ({
|
||||
resolveImportErrorsMock.mockImplementation(() => ({
|
||||
status: 'success',
|
||||
importCount: 0,
|
||||
failedImports: [
|
||||
|
@ -303,18 +293,15 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
describe('errors', () => {
|
||||
const { importFile } = require('../../../../../lib/import_file');
|
||||
const { resolveImportErrors } = require('../../../../../lib/resolve_import_errors');
|
||||
|
||||
it('should display unsupported type errors properly', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await Promise.resolve();
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
importFile.mockImplementation(() => ({
|
||||
importFileMock.mockImplementation(() => ({
|
||||
success: false,
|
||||
successCount: 0,
|
||||
errors: [
|
||||
|
@ -328,7 +315,7 @@ describe('Flyout', () => {
|
|||
},
|
||||
],
|
||||
}));
|
||||
resolveImportErrors.mockImplementation(() => ({
|
||||
resolveImportErrorsMock.mockImplementation(() => ({
|
||||
status: 'success',
|
||||
importCount: 0,
|
||||
failedImports: [
|
||||
|
@ -372,14 +359,6 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
describe('legacy conflicts', () => {
|
||||
const { importLegacyFile } = require('../../../../../lib/import_legacy_file');
|
||||
const {
|
||||
resolveSavedObjects,
|
||||
resolveSavedSearches,
|
||||
resolveIndexPatternConflicts,
|
||||
saveObjects,
|
||||
} = require('../../../../../lib/resolve_saved_objects');
|
||||
|
||||
const mockData = [
|
||||
{
|
||||
_id: '1',
|
||||
|
@ -406,7 +385,7 @@ describe('Flyout', () => {
|
|||
},
|
||||
obj: {
|
||||
searchSource: {
|
||||
getOwnField: field => {
|
||||
getOwnField: (field: string) => {
|
||||
if (field === 'index') {
|
||||
return 'MyIndexPattern*';
|
||||
}
|
||||
|
@ -426,8 +405,8 @@ describe('Flyout', () => {
|
|||
const mockConflictedSearchDocs = [3];
|
||||
|
||||
beforeEach(() => {
|
||||
importLegacyFile.mockImplementation(() => mockData);
|
||||
resolveSavedObjects.mockImplementation(() => ({
|
||||
importLegacyFileMock.mockImplementation(() => mockData);
|
||||
resolveSavedObjectsMock.mockImplementation(() => ({
|
||||
conflictedIndexPatterns: mockConflictedIndexPatterns,
|
||||
conflictedSavedObjectsLinkedToSavedSearches: mockConflictedSavedObjectsLinkedToSavedSearches,
|
||||
conflictedSearchDocs: mockConflictedSearchDocs,
|
||||
|
@ -437,7 +416,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should figure out unmatchedReferences', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -447,14 +426,14 @@ describe('Flyout', () => {
|
|||
component.setState({ file: legacyMockFile, isLegacyFile: true });
|
||||
await component.instance().legacyImport();
|
||||
|
||||
expect(importLegacyFile).toHaveBeenCalledWith(legacyMockFile);
|
||||
expect(importLegacyFileMock).toHaveBeenCalledWith(legacyMockFile);
|
||||
// Remove the last element from data since it should be filtered out
|
||||
expect(resolveSavedObjects).toHaveBeenCalledWith(
|
||||
expect(resolveSavedObjectsMock).toHaveBeenCalledWith(
|
||||
mockData.slice(0, 2).map(doc => ({ ...doc, _migrationVersion: {} })),
|
||||
true,
|
||||
defaultProps.services,
|
||||
defaultProps.serviceRegistry.all().map(s => s.service),
|
||||
defaultProps.indexPatterns,
|
||||
defaultProps.confirmModalPromise
|
||||
defaultProps.overlays.openConfirm
|
||||
);
|
||||
|
||||
expect(component.state()).toMatchObject({
|
||||
|
@ -492,7 +471,7 @@ describe('Flyout', () => {
|
|||
});
|
||||
|
||||
it('should allow conflict resolution', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -508,7 +487,7 @@ describe('Flyout', () => {
|
|||
|
||||
// Ensure we can change the resolution
|
||||
component.instance().onIndexChanged('MyIndexPattern*', { target: { value: '2' } });
|
||||
expect(component.state('unmatchedReferences')[0].newIndexPatternId).toBe('2');
|
||||
expect(component.state('unmatchedReferences')![0].newIndexPatternId).toBe('2');
|
||||
|
||||
// Let's resolve now
|
||||
await component
|
||||
|
@ -516,33 +495,33 @@ describe('Flyout', () => {
|
|||
.simulate('click');
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
expect(resolveIndexPatternConflicts).toHaveBeenCalledWith(
|
||||
expect(resolveIndexPatternConflictsMock).toHaveBeenCalledWith(
|
||||
component.instance().resolutions,
|
||||
mockConflictedIndexPatterns,
|
||||
true,
|
||||
defaultProps.indexPatterns
|
||||
);
|
||||
expect(saveObjects).toHaveBeenCalledWith(
|
||||
expect(saveObjectsMock).toHaveBeenCalledWith(
|
||||
mockConflictedSavedObjectsLinkedToSavedSearches,
|
||||
true
|
||||
);
|
||||
expect(resolveSavedSearches).toHaveBeenCalledWith(
|
||||
expect(resolveSavedSearchesMock).toHaveBeenCalledWith(
|
||||
mockConflictedSearchDocs,
|
||||
defaultProps.services,
|
||||
defaultProps.serviceRegistry.all().map(s => s.service),
|
||||
defaultProps.indexPatterns,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors', async () => {
|
||||
const component = shallowWithI18nProvider(<Flyout {...defaultProps} />);
|
||||
const component = shallowRender(defaultProps);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
resolveIndexPatternConflicts.mockImplementation(() => {
|
||||
resolveIndexPatternConflictsMock.mockImplementation(() => {
|
||||
throw new Error('foobar');
|
||||
});
|
||||
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { take, get as getField } from 'lodash';
|
||||
import {
|
||||
EuiFlyout,
|
||||
|
@ -32,6 +31,7 @@ import {
|
|||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSwitch,
|
||||
// @ts-ignore
|
||||
EuiFilePicker,
|
||||
EuiInMemoryTable,
|
||||
EuiSelect,
|
||||
|
@ -47,34 +47,62 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { OverlayStart, HttpStart } from 'src/core/public';
|
||||
import { IndexPatternsContract, IIndexPattern } from '../../../../../data/public';
|
||||
import {
|
||||
importFile,
|
||||
importLegacyFile,
|
||||
resolveImportErrors,
|
||||
logLegacyImport,
|
||||
getDefaultTitle,
|
||||
} from '../../../../lib';
|
||||
import { processImportResponse } from '../../../../lib/process_import_response';
|
||||
processImportResponse,
|
||||
ProcessedImportResponse,
|
||||
} from '../../../lib';
|
||||
import {
|
||||
resolveSavedObjects,
|
||||
resolveSavedSearches,
|
||||
resolveIndexPatternConflicts,
|
||||
saveObjects,
|
||||
} from '../../../../lib/resolve_saved_objects';
|
||||
import { POSSIBLE_TYPES } from '../../objects_table';
|
||||
} from '../../../lib/resolve_saved_objects';
|
||||
import { ISavedObjectsManagementServiceRegistry } from '../../../services';
|
||||
|
||||
export class Flyout extends Component {
|
||||
static propTypes = {
|
||||
close: PropTypes.func.isRequired,
|
||||
done: PropTypes.func.isRequired,
|
||||
services: PropTypes.array.isRequired,
|
||||
newIndexPatternUrl: PropTypes.string.isRequired,
|
||||
indexPatterns: PropTypes.object.isRequired,
|
||||
confirmModalPromise: PropTypes.func.isRequired,
|
||||
};
|
||||
export interface FlyoutProps {
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
allowedTypes: string[];
|
||||
close: () => void;
|
||||
done: () => void;
|
||||
newIndexPatternUrl: string;
|
||||
indexPatterns: IndexPatternsContract;
|
||||
overlays: OverlayStart;
|
||||
http: HttpStart;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
export interface FlyoutState {
|
||||
conflictedIndexPatterns?: any[];
|
||||
conflictedSavedObjectsLinkedToSavedSearches?: any[];
|
||||
conflictedSearchDocs?: any[];
|
||||
unmatchedReferences?: ProcessedImportResponse['unmatchedReferences'];
|
||||
failedImports?: ProcessedImportResponse['failedImports'];
|
||||
conflictingRecord?: ConflictingRecord;
|
||||
error?: string;
|
||||
file?: File;
|
||||
importCount: number;
|
||||
indexPatterns?: IIndexPattern[];
|
||||
isOverwriteAllChecked: boolean;
|
||||
loadingMessage?: string;
|
||||
isLegacyFile: boolean;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface ConflictingRecord {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
done: (success: boolean) => void;
|
||||
}
|
||||
|
||||
export class Flyout extends Component<FlyoutProps, FlyoutState> {
|
||||
constructor(props: FlyoutProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -100,7 +128,7 @@ export class Flyout extends Component {
|
|||
|
||||
fetchIndexPatterns = async () => {
|
||||
const indexPatterns = await this.props.indexPatterns.getFields(['id', 'title']);
|
||||
this.setState({ indexPatterns });
|
||||
this.setState({ indexPatterns } as any);
|
||||
};
|
||||
|
||||
changeOverwriteAll = () => {
|
||||
|
@ -109,11 +137,12 @@ export class Flyout extends Component {
|
|||
}));
|
||||
};
|
||||
|
||||
setImportFile = ([file]) => {
|
||||
if (!file) {
|
||||
setImportFile = (files: FileList | null) => {
|
||||
if (!files || !files[0]) {
|
||||
this.setState({ file: undefined, isLegacyFile: false });
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
this.setState({
|
||||
file,
|
||||
isLegacyFile: /\.json$/i.test(file.name) || file.type === 'application/json',
|
||||
|
@ -126,30 +155,29 @@ export class Flyout extends Component {
|
|||
* Does the initial import of a file, resolveImportErrors then handles errors and retries
|
||||
*/
|
||||
import = async () => {
|
||||
const { http } = this.props;
|
||||
const { file, isOverwriteAllChecked } = this.state;
|
||||
this.setState({ status: 'loading', error: undefined });
|
||||
|
||||
// Import the file
|
||||
let response;
|
||||
try {
|
||||
response = await importFile(file, isOverwriteAllChecked);
|
||||
const response = await importFile(http, file!, isOverwriteAllChecked);
|
||||
this.setState(processImportResponse(response), () => {
|
||||
// Resolve import errors right away if there's no index patterns to match
|
||||
// This will ask about overwriting each object, etc
|
||||
if (this.state.unmatchedReferences?.length === 0) {
|
||||
this.resolveImportErrors();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
status: 'error',
|
||||
error: i18n.translate('kbn.management.objects.objectsTable.flyout.importFileErrorMessage', {
|
||||
error: i18n.translate('savedObjectsManagement.objectsTable.flyout.importFileErrorMessage', {
|
||||
defaultMessage: 'The file could not be processed.',
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(processImportResponse(response), () => {
|
||||
// Resolve import errors right away if there's no index patterns to match
|
||||
// This will ask about overwriting each object, etc
|
||||
if (this.state.unmatchedReferences.length === 0) {
|
||||
this.resolveImportErrors();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -160,10 +188,10 @@ export class Flyout extends Component {
|
|||
* @param {array} objects List of objects to request the user if they wish to overwrite it
|
||||
* @return {Promise<array>} An object with the key being "type:id" and value the resolution chosen by the user
|
||||
*/
|
||||
getConflictResolutions = async objects => {
|
||||
const resolutions = {};
|
||||
getConflictResolutions = async (objects: any[]) => {
|
||||
const resolutions: Record<string, boolean> = {};
|
||||
for (const { type, id, title } of objects) {
|
||||
const overwrite = await new Promise(resolve => {
|
||||
const overwrite = await new Promise<boolean>(resolve => {
|
||||
this.setState({
|
||||
conflictingRecord: {
|
||||
id,
|
||||
|
@ -193,6 +221,7 @@ export class Flyout extends Component {
|
|||
|
||||
try {
|
||||
const updatedState = await resolveImportErrors({
|
||||
http: this.props.http,
|
||||
state: this.state,
|
||||
getConflictResolutions: this.getConflictResolutions,
|
||||
});
|
||||
|
@ -201,7 +230,7 @@ export class Flyout extends Component {
|
|||
this.setState({
|
||||
status: 'error',
|
||||
error: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.resolveImportErrorsFileErrorMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.resolveImportErrorsFileErrorMessage',
|
||||
{ defaultMessage: 'The file could not be processed.' }
|
||||
),
|
||||
});
|
||||
|
@ -209,22 +238,22 @@ export class Flyout extends Component {
|
|||
};
|
||||
|
||||
legacyImport = async () => {
|
||||
const { services, indexPatterns, confirmModalPromise } = this.props;
|
||||
const { serviceRegistry, indexPatterns, overlays, http, allowedTypes } = this.props;
|
||||
const { file, isOverwriteAllChecked } = this.state;
|
||||
|
||||
this.setState({ status: 'loading', error: undefined });
|
||||
|
||||
// Log warning on server, don't wait for response
|
||||
logLegacyImport();
|
||||
logLegacyImport(http);
|
||||
|
||||
let contents;
|
||||
try {
|
||||
contents = await importLegacyFile(file);
|
||||
contents = await importLegacyFile(file!);
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
status: 'error',
|
||||
error: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.importLegacyFileErrorMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.importLegacyFileErrorMessage',
|
||||
{ defaultMessage: 'The file could not be processed.' }
|
||||
),
|
||||
});
|
||||
|
@ -235,7 +264,7 @@ export class Flyout extends Component {
|
|||
this.setState({
|
||||
status: 'error',
|
||||
error: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage',
|
||||
{ defaultMessage: 'Saved objects file format is invalid and cannot be imported.' }
|
||||
),
|
||||
});
|
||||
|
@ -243,7 +272,7 @@ export class Flyout extends Component {
|
|||
}
|
||||
|
||||
contents = contents
|
||||
.filter(content => POSSIBLE_TYPES.includes(content._type))
|
||||
.filter(content => allowedTypes.includes(content._type))
|
||||
.map(doc => ({
|
||||
...doc,
|
||||
// The server assumes that documents with no migrationVersion are up to date.
|
||||
|
@ -263,18 +292,18 @@ export class Flyout extends Component {
|
|||
} = await resolveSavedObjects(
|
||||
contents,
|
||||
isOverwriteAllChecked,
|
||||
services,
|
||||
serviceRegistry.all().map(e => e.service),
|
||||
indexPatterns,
|
||||
confirmModalPromise
|
||||
overlays.openConfirm
|
||||
);
|
||||
|
||||
const byId = {};
|
||||
const byId: Record<string, any[]> = {};
|
||||
conflictedIndexPatterns
|
||||
.map(({ doc, obj }) => {
|
||||
return { doc, obj: obj._serialize() };
|
||||
})
|
||||
.forEach(({ doc, obj }) =>
|
||||
obj.references.forEach(ref => {
|
||||
obj.references.forEach((ref: Record<string, any>) => {
|
||||
byId[ref.id] = byId[ref.id] != null ? byId[ref.id].concat({ doc, obj }) : [{ doc, obj }];
|
||||
})
|
||||
);
|
||||
|
@ -291,7 +320,7 @@ export class Flyout extends Component {
|
|||
});
|
||||
return accum;
|
||||
},
|
||||
[]
|
||||
[] as any[]
|
||||
);
|
||||
|
||||
this.setState({
|
||||
|
@ -305,12 +334,12 @@ export class Flyout extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
get hasUnmatchedReferences() {
|
||||
public get hasUnmatchedReferences() {
|
||||
return this.state.unmatchedReferences && this.state.unmatchedReferences.length > 0;
|
||||
}
|
||||
|
||||
get resolutions() {
|
||||
return this.state.unmatchedReferences.reduce(
|
||||
public get resolutions() {
|
||||
return this.state.unmatchedReferences!.reduce(
|
||||
(accum, { existingIndexPatternId, newIndexPatternId }) => {
|
||||
if (newIndexPatternId) {
|
||||
accum.push({
|
||||
|
@ -320,7 +349,7 @@ export class Flyout extends Component {
|
|||
}
|
||||
return accum;
|
||||
},
|
||||
[]
|
||||
[] as Array<{ oldId: string; newId: string }>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -333,7 +362,7 @@ export class Flyout extends Component {
|
|||
failedImports,
|
||||
} = this.state;
|
||||
|
||||
const { services, indexPatterns } = this.props;
|
||||
const { serviceRegistry, indexPatterns } = this.props;
|
||||
|
||||
this.setState({
|
||||
error: undefined,
|
||||
|
@ -350,48 +379,48 @@ export class Flyout extends Component {
|
|||
// Do not Promise.all these calls as the order matters
|
||||
this.setState({
|
||||
loadingMessage: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.resolvingConflictsLoadingMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.resolvingConflictsLoadingMessage',
|
||||
{ defaultMessage: 'Resolving conflicts…' }
|
||||
),
|
||||
});
|
||||
if (resolutions.length) {
|
||||
importCount += await resolveIndexPatternConflicts(
|
||||
resolutions,
|
||||
conflictedIndexPatterns,
|
||||
conflictedIndexPatterns!,
|
||||
isOverwriteAllChecked,
|
||||
this.props.indexPatterns
|
||||
indexPatterns
|
||||
);
|
||||
}
|
||||
this.setState({
|
||||
loadingMessage: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savingConflictsLoadingMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.savingConflictsLoadingMessage',
|
||||
{ defaultMessage: 'Saving conflicts…' }
|
||||
),
|
||||
});
|
||||
importCount += await saveObjects(
|
||||
conflictedSavedObjectsLinkedToSavedSearches,
|
||||
conflictedSavedObjectsLinkedToSavedSearches!,
|
||||
isOverwriteAllChecked
|
||||
);
|
||||
this.setState({
|
||||
loadingMessage: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savedSearchAreLinkedProperlyLoadingMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.savedSearchAreLinkedProperlyLoadingMessage',
|
||||
{ defaultMessage: 'Ensure saved searches are linked properly…' }
|
||||
),
|
||||
});
|
||||
importCount += await resolveSavedSearches(
|
||||
conflictedSearchDocs,
|
||||
services,
|
||||
conflictedSearchDocs!,
|
||||
serviceRegistry.all().map(e => e.service),
|
||||
indexPatterns,
|
||||
isOverwriteAllChecked
|
||||
);
|
||||
this.setState({
|
||||
loadingMessage: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.retryingFailedObjectsLoadingMessage',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.retryingFailedObjectsLoadingMessage',
|
||||
{ defaultMessage: 'Retrying failed objects…' }
|
||||
),
|
||||
});
|
||||
importCount += await saveObjects(
|
||||
failedImports.map(({ obj }) => obj),
|
||||
failedImports!.map(({ obj }) => obj) as any[],
|
||||
isOverwriteAllChecked
|
||||
);
|
||||
} catch (e) {
|
||||
|
@ -407,26 +436,26 @@ export class Flyout extends Component {
|
|||
this.setState({ status: 'success', importCount });
|
||||
};
|
||||
|
||||
onIndexChanged = (id, e) => {
|
||||
onIndexChanged = (id: string, e: any) => {
|
||||
const value = e.target.value;
|
||||
this.setState(state => {
|
||||
const conflictIndex = state.unmatchedReferences.findIndex(
|
||||
const conflictIndex = state.unmatchedReferences?.findIndex(
|
||||
conflict => conflict.existingIndexPatternId === id
|
||||
);
|
||||
if (conflictIndex === -1) {
|
||||
if (conflictIndex === undefined || conflictIndex === -1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
unmatchedReferences: [
|
||||
...state.unmatchedReferences.slice(0, conflictIndex),
|
||||
...state.unmatchedReferences!.slice(0, conflictIndex),
|
||||
{
|
||||
...state.unmatchedReferences[conflictIndex],
|
||||
...state.unmatchedReferences![conflictIndex],
|
||||
newIndexPatternId: value,
|
||||
},
|
||||
...state.unmatchedReferences.slice(conflictIndex + 1),
|
||||
...state.unmatchedReferences!.slice(conflictIndex + 1),
|
||||
],
|
||||
};
|
||||
} as any;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -441,11 +470,11 @@ export class Flyout extends Component {
|
|||
{
|
||||
field: 'existingIndexPatternId',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdName',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnIdName',
|
||||
{ defaultMessage: 'ID' }
|
||||
),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdDescription',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnIdDescription',
|
||||
{ defaultMessage: 'ID of the index pattern' }
|
||||
),
|
||||
sortable: true,
|
||||
|
@ -453,28 +482,28 @@ export class Flyout extends Component {
|
|||
{
|
||||
field: 'list',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountName',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnCountName',
|
||||
{ defaultMessage: 'Count' }
|
||||
),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountDescription',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnCountDescription',
|
||||
{ defaultMessage: 'How many affected objects' }
|
||||
),
|
||||
render: list => {
|
||||
render: (list: any[]) => {
|
||||
return <Fragment>{list.length}</Fragment>;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'list',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName',
|
||||
{ defaultMessage: 'Sample of affected objects' }
|
||||
),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription',
|
||||
{ defaultMessage: 'Sample of affected objects' }
|
||||
),
|
||||
render: list => {
|
||||
render: (list: any[]) => {
|
||||
return (
|
||||
<ul style={{ listStyle: 'none' }}>
|
||||
{take(list, 3).map((obj, key) => (
|
||||
|
@ -487,15 +516,18 @@ export class Flyout extends Component {
|
|||
{
|
||||
field: 'existingIndexPatternId',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.renderConflicts.columnNewIndexPatternName',
|
||||
'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnNewIndexPatternName',
|
||||
{ defaultMessage: 'New index pattern' }
|
||||
),
|
||||
render: id => {
|
||||
const options = this.state.indexPatterns.map(indexPattern => ({
|
||||
text: indexPattern.title,
|
||||
value: indexPattern.id,
|
||||
['data-test-subj']: `indexPatternOption-${indexPattern.title}`,
|
||||
}));
|
||||
render: (id: string) => {
|
||||
const options = this.state.indexPatterns!.map(
|
||||
indexPattern =>
|
||||
({
|
||||
text: indexPattern.title,
|
||||
value: indexPattern.id,
|
||||
'data-test-subj': `indexPatternOption-${indexPattern.title}`,
|
||||
} as { text: string; value: string; 'data-test-subj'?: string })
|
||||
);
|
||||
|
||||
options.unshift({
|
||||
text: '-- Skip Import --',
|
||||
|
@ -518,7 +550,11 @@ export class Flyout extends Component {
|
|||
};
|
||||
|
||||
return (
|
||||
<EuiInMemoryTable items={unmatchedReferences} columns={columns} pagination={pagination} />
|
||||
<EuiInMemoryTable
|
||||
items={unmatchedReferences as any[]}
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -534,7 +570,7 @@ export class Flyout extends Component {
|
|||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.errorCalloutTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.errorCalloutTitle"
|
||||
defaultMessage="Sorry, there was an error"
|
||||
/>
|
||||
}
|
||||
|
@ -581,7 +617,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsFailedWarning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importFailedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importFailedTitle"
|
||||
defaultMessage="Import failed"
|
||||
/>
|
||||
}
|
||||
|
@ -590,7 +626,7 @@ export class Flyout extends Component {
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importFailedDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importFailedDescription"
|
||||
defaultMessage="Failed to import {failedImportCount} of {totalImportCount} objects. Import failed"
|
||||
values={{
|
||||
failedImportCount: failedImports.length,
|
||||
|
@ -604,7 +640,7 @@ export class Flyout extends Component {
|
|||
if (error.type === 'missing_references') {
|
||||
return error.references.map(reference => {
|
||||
return i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.importFailedMissingReference',
|
||||
'savedObjectsManagement.objectsTable.flyout.importFailedMissingReference',
|
||||
{
|
||||
defaultMessage: '{type} [id={id}] could not locate {refType} [id={refId}]',
|
||||
values: {
|
||||
|
@ -618,7 +654,7 @@ export class Flyout extends Component {
|
|||
});
|
||||
} else if (error.type === 'unsupported_type') {
|
||||
return i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.importFailedUnsupportedType',
|
||||
'savedObjectsManagement.objectsTable.flyout.importFailedUnsupportedType',
|
||||
{
|
||||
defaultMessage: '{type} [id={id}] unsupported type',
|
||||
values: {
|
||||
|
@ -628,7 +664,7 @@ export class Flyout extends Component {
|
|||
}
|
||||
);
|
||||
}
|
||||
return getField(error, 'body.message', error.message || '');
|
||||
return getField(error, 'body.message', (error as any).message ?? '');
|
||||
})
|
||||
.join(' ')}
|
||||
</p>
|
||||
|
@ -643,7 +679,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsSuccessNoneImported"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessfulCallout.noObjectsImportedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessfulCallout.noObjectsImportedTitle"
|
||||
defaultMessage="No objects imported"
|
||||
/>
|
||||
}
|
||||
|
@ -657,7 +693,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsSuccess"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessfulTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessfulTitle"
|
||||
defaultMessage="Import successful"
|
||||
/>
|
||||
}
|
||||
|
@ -666,7 +702,7 @@ export class Flyout extends Component {
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessfulDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessfulDescription"
|
||||
defaultMessage="Successfully imported {importCount} objects."
|
||||
values={{ importCount }}
|
||||
/>
|
||||
|
@ -684,7 +720,7 @@ export class Flyout extends Component {
|
|||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.selectFileToImportFormRowLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.selectFileToImportFormRowLabel"
|
||||
defaultMessage="Please select a file to import"
|
||||
/>
|
||||
}
|
||||
|
@ -692,7 +728,7 @@ export class Flyout extends Component {
|
|||
<EuiFilePicker
|
||||
initialPromptText={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importPromptText"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importPromptText"
|
||||
defaultMessage="Import"
|
||||
/>
|
||||
}
|
||||
|
@ -704,7 +740,7 @@ export class Flyout extends Component {
|
|||
name="overwriteAll"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.overwriteSavedObjectsLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.overwriteSavedObjectsLabel"
|
||||
defaultMessage="Automatically overwrite all saved objects?"
|
||||
/>
|
||||
}
|
||||
|
@ -727,7 +763,7 @@ export class Flyout extends Component {
|
|||
confirmButton = (
|
||||
<EuiButton onClick={done} size="s" fill data-test-subj="importSavedObjectsDoneBtn">
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessful.confirmButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessful.confirmButtonLabel"
|
||||
defaultMessage="Done"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -742,7 +778,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsConfirmBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel"
|
||||
defaultMessage="Confirm all changes"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -757,7 +793,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsImportBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.import.confirmButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.import.confirmButtonLabel"
|
||||
defaultMessage="Import"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -769,7 +805,7 @@ export class Flyout extends Component {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={close} size="s">
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.import.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.flyout.import.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -791,7 +827,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsLegacyWarning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.legacyFileUsedTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.legacyFileUsedTitle"
|
||||
defaultMessage="Support for JSON files is going away"
|
||||
/>
|
||||
}
|
||||
|
@ -800,7 +836,7 @@ export class Flyout extends Component {
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.legacyFileUsedBody"
|
||||
id="savedObjectsManagement.objectsTable.flyout.legacyFileUsedBody"
|
||||
defaultMessage="Use our updated export to generate NDJSON files, and you'll be all set."
|
||||
/>
|
||||
</p>
|
||||
|
@ -815,7 +851,7 @@ export class Flyout extends Component {
|
|||
data-test-subj="importSavedObjectsConflictsWarning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsTitle"
|
||||
defaultMessage="Index Pattern Conflicts"
|
||||
/>
|
||||
}
|
||||
|
@ -824,7 +860,7 @@ export class Flyout extends Component {
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsDescription"
|
||||
defaultMessage="The following saved objects use index patterns that do not exist.
|
||||
Please select the index patterns you'd like re-associated with
|
||||
them. You can {indexPatternLink} if necessary."
|
||||
|
@ -832,7 +868,7 @@ export class Flyout extends Component {
|
|||
indexPatternLink: (
|
||||
<EuiLink href={this.props.newIndexPatternUrl}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
id="savedObjectsManagement.objectsTable.flyout.indexPatternConflictsCalloutLinkText"
|
||||
defaultMessage="create a new index pattern"
|
||||
/>
|
||||
</EuiLink>
|
||||
|
@ -867,11 +903,11 @@ export class Flyout extends Component {
|
|||
}
|
||||
|
||||
overwriteConfirmed() {
|
||||
this.state.conflictingRecord.done(true);
|
||||
this.state.conflictingRecord!.done(true);
|
||||
}
|
||||
|
||||
overwriteSkipped() {
|
||||
this.state.conflictingRecord.done(false);
|
||||
this.state.conflictingRecord!.done(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -883,18 +919,18 @@ export class Flyout extends Component {
|
|||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
title={i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmOverwriteTitle',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmOverwriteTitle',
|
||||
{
|
||||
defaultMessage: 'Overwrite {type}?',
|
||||
values: { type: this.state.conflictingRecord.type },
|
||||
}
|
||||
)}
|
||||
cancelButtonText={i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmOverwriteCancelButtonText',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmOverwriteCancelButtonText',
|
||||
{ defaultMessage: 'Cancel' }
|
||||
)}
|
||||
confirmButtonText={i18n.translate(
|
||||
'kbn.management.objects.objectsTable.flyout.confirmOverwriteOverwriteButtonText',
|
||||
'savedObjectsManagement.objectsTable.flyout.confirmOverwriteOverwriteButtonText',
|
||||
{ defaultMessage: 'Overwrite' }
|
||||
)}
|
||||
buttonColor="danger"
|
||||
|
@ -904,7 +940,7 @@ export class Flyout extends Component {
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.confirmOverwriteBody"
|
||||
id="savedObjectsManagement.objectsTable.flyout.confirmOverwriteBody"
|
||||
defaultMessage="Are you sure you want to overwrite {title}?"
|
||||
values={{
|
||||
title:
|
||||
|
@ -924,7 +960,7 @@ export class Flyout extends Component {
|
|||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.flyout.importSavedObjectTitle"
|
||||
id="savedObjectsManagement.objectsTable.flyout.importSavedObjectTitle"
|
||||
defaultMessage="Import saved objects"
|
||||
/>
|
||||
</h2>
|
|
@ -19,8 +19,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { Header } from '../header';
|
||||
import { Header } from './header';
|
||||
|
||||
describe('Header', () => {
|
||||
it('should render normally', () => {
|
|
@ -18,8 +18,6 @@
|
|||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
|
@ -31,14 +29,24 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (
|
||||
export const Header = ({
|
||||
onExportAll,
|
||||
onImport,
|
||||
onRefresh,
|
||||
filteredCount,
|
||||
}: {
|
||||
onExportAll: () => void;
|
||||
onImport: () => void;
|
||||
onRefresh: () => void;
|
||||
filteredCount: number;
|
||||
}) => (
|
||||
<Fragment>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="baseline">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.header.savedObjectsTitle"
|
||||
id="savedObjectsManagement.objectsTable.header.savedObjectsTitle"
|
||||
defaultMessage="Saved Objects"
|
||||
/>
|
||||
</h1>
|
||||
|
@ -55,7 +63,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (
|
|||
onClick={onExportAll}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.header.exportButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.header.exportButtonLabel"
|
||||
defaultMessage="Export {filteredCount, plural, one{# object} other {# objects}}"
|
||||
values={{
|
||||
filteredCount,
|
||||
|
@ -71,7 +79,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (
|
|||
onClick={onImport}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.header.importButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.header.importButtonLabel"
|
||||
defaultMessage="Import"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -79,7 +87,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty size="s" iconType="refresh" onClick={onRefresh}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.header.refreshButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.header.refreshButtonLabel"
|
||||
defaultMessage="Refresh"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -92,7 +100,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (
|
|||
<p>
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.howToDeleteSavedObjectsDescription"
|
||||
id="savedObjectsManagement.objectsTable.howToDeleteSavedObjectsDescription"
|
||||
defaultMessage="From here you can delete saved objects, such as saved searches.
|
||||
You can also edit the raw data of saved objects.
|
||||
Typically objects are only modified via their associated application,
|
||||
|
@ -104,10 +112,3 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (
|
|||
<EuiSpacer size="m" />
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
Header.propTypes = {
|
||||
onExportAll: PropTypes.func.isRequired,
|
||||
onImport: PropTypes.func.isRequired,
|
||||
onRefresh: PropTypes.func.isRequired,
|
||||
filteredCount: PropTypes.number.isRequired,
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { Header } from './header';
|
||||
export { Table } from './table';
|
||||
export { Flyout } from './flyout';
|
||||
export { Relationships } from './relationships';
|
|
@ -19,27 +19,23 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers';
|
||||
import { httpServiceMock } from '../../../../../../core/public/mocks';
|
||||
import { Relationships, RelationshipsProps } from './relationships';
|
||||
|
||||
jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() }));
|
||||
|
||||
jest.mock('ui/chrome', () => ({
|
||||
addBasePath: () => '',
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../lib/fetch_export_by_type_and_search', () => ({
|
||||
jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({
|
||||
fetchExportByTypeAndSearch: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../lib/fetch_export_objects', () => ({
|
||||
jest.mock('../../../lib/fetch_export_objects', () => ({
|
||||
fetchExportObjects: jest.fn(),
|
||||
}));
|
||||
|
||||
import { Relationships } from '../relationships';
|
||||
|
||||
describe('Relationships', () => {
|
||||
it('should render index patterns normally', async () => {
|
||||
const props = {
|
||||
const props: RelationshipsProps = {
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => true,
|
||||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
getRelationships: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
type: 'search',
|
||||
|
@ -73,6 +69,8 @@ describe('Relationships', () => {
|
|||
savedObject: {
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: 'MyIndexPattern*',
|
||||
icon: 'indexPatternApp',
|
||||
|
@ -101,8 +99,10 @@ describe('Relationships', () => {
|
|||
});
|
||||
|
||||
it('should render searches normally', async () => {
|
||||
const props = {
|
||||
const props: RelationshipsProps = {
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => true,
|
||||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
getRelationships: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
|
@ -136,6 +136,8 @@ describe('Relationships', () => {
|
|||
savedObject: {
|
||||
id: '1',
|
||||
type: 'search',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: 'MySearch',
|
||||
icon: 'search',
|
||||
|
@ -164,8 +166,10 @@ describe('Relationships', () => {
|
|||
});
|
||||
|
||||
it('should render visualizations normally', async () => {
|
||||
const props = {
|
||||
const props: RelationshipsProps = {
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => true,
|
||||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
getRelationships: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
type: 'dashboard',
|
||||
|
@ -199,6 +203,8 @@ describe('Relationships', () => {
|
|||
savedObject: {
|
||||
id: '1',
|
||||
type: 'visualization',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: 'MyViz',
|
||||
icon: 'visualizeApp',
|
||||
|
@ -227,8 +233,10 @@ describe('Relationships', () => {
|
|||
});
|
||||
|
||||
it('should render dashboards normally', async () => {
|
||||
const props = {
|
||||
const props: RelationshipsProps = {
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => true,
|
||||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
getRelationships: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
type: 'visualization',
|
||||
|
@ -262,6 +270,8 @@ describe('Relationships', () => {
|
|||
savedObject: {
|
||||
id: '1',
|
||||
type: 'dashboard',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: 'MyDashboard',
|
||||
icon: 'dashboardApp',
|
||||
|
@ -290,14 +300,18 @@ describe('Relationships', () => {
|
|||
});
|
||||
|
||||
it('should render errors', async () => {
|
||||
const props = {
|
||||
const props: RelationshipsProps = {
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => true,
|
||||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
getRelationships: jest.fn().mockImplementation(() => {
|
||||
throw new Error('foo');
|
||||
}),
|
||||
savedObject: {
|
||||
id: '1',
|
||||
type: 'dashboard',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: 'MyDashboard',
|
||||
icon: 'dashboardApp',
|
|
@ -18,8 +18,6 @@
|
|||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiTitle,
|
||||
EuiFlyout,
|
||||
|
@ -34,25 +32,34 @@ import {
|
|||
EuiText,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import chrome from 'ui/chrome';
|
||||
import { FilterConfig } from '@elastic/eui/src/components/search_bar/filters/filters';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getDefaultTitle, getSavedObjectLabel } from '../../../../lib';
|
||||
import { IBasePath } from 'src/core/public';
|
||||
import { getDefaultTitle, getSavedObjectLabel } from '../../../lib';
|
||||
import { SavedObjectWithMetadata, SavedObjectRelation } from '../../../types';
|
||||
|
||||
export class Relationships extends Component {
|
||||
static propTypes = {
|
||||
getRelationships: PropTypes.func.isRequired,
|
||||
savedObject: PropTypes.object.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
goInspectObject: PropTypes.func.isRequired,
|
||||
canGoInApp: PropTypes.func.isRequired,
|
||||
};
|
||||
export interface RelationshipsProps {
|
||||
basePath: IBasePath;
|
||||
getRelationships: (type: string, id: string) => Promise<SavedObjectRelation[]>;
|
||||
savedObject: SavedObjectWithMetadata;
|
||||
close: () => void;
|
||||
goInspectObject: (obj: SavedObjectWithMetadata) => void;
|
||||
canGoInApp: (obj: SavedObjectWithMetadata) => boolean;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
export interface RelationshipsState {
|
||||
relationships: SavedObjectRelation[];
|
||||
isLoading: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class Relationships extends Component<RelationshipsProps, RelationshipsState> {
|
||||
constructor(props: RelationshipsProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
relationships: undefined,
|
||||
relationships: [],
|
||||
isLoading: false,
|
||||
error: undefined,
|
||||
};
|
||||
|
@ -62,7 +69,7 @@ export class Relationships extends Component {
|
|||
this.getRelationshipData();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: RelationshipsProps) {
|
||||
if (nextProps.savedObject.id !== this.props.savedObject.id) {
|
||||
this.getRelationshipData();
|
||||
}
|
||||
|
@ -92,7 +99,7 @@ export class Relationships extends Component {
|
|||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.relationships.renderErrorMessage"
|
||||
id="savedObjectsManagement.objectsTable.relationships.renderErrorMessage"
|
||||
defaultMessage="Error"
|
||||
/>
|
||||
}
|
||||
|
@ -104,7 +111,7 @@ export class Relationships extends Component {
|
|||
}
|
||||
|
||||
renderRelationships() {
|
||||
const { goInspectObject, savedObject } = this.props;
|
||||
const { goInspectObject, savedObject, basePath } = this.props;
|
||||
const { relationships, isLoading, error } = this.state;
|
||||
|
||||
if (error) {
|
||||
|
@ -118,17 +125,17 @@ export class Relationships extends Component {
|
|||
const columns = [
|
||||
{
|
||||
field: 'type',
|
||||
name: i18n.translate('kbn.management.objects.objectsTable.relationships.columnTypeName', {
|
||||
name: i18n.translate('savedObjectsManagement.objectsTable.relationships.columnTypeName', {
|
||||
defaultMessage: 'Type',
|
||||
}),
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.columnTypeDescription',
|
||||
'savedObjectsManagement.objectsTable.relationships.columnTypeDescription',
|
||||
{ defaultMessage: 'Type of the saved object' }
|
||||
),
|
||||
sortable: false,
|
||||
render: (type, object) => {
|
||||
render: (type: string, object: SavedObjectWithMetadata) => {
|
||||
return (
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
|
||||
<EuiIcon
|
||||
|
@ -144,19 +151,19 @@ export class Relationships extends Component {
|
|||
{
|
||||
field: 'relationship',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.columnRelationshipName',
|
||||
'savedObjectsManagement.objectsTable.relationships.columnRelationshipName',
|
||||
{ defaultMessage: 'Direct relationship' }
|
||||
),
|
||||
dataType: 'string',
|
||||
sortable: false,
|
||||
width: '125px',
|
||||
'data-test-subj': 'directRelationship',
|
||||
render: relationship => {
|
||||
render: (relationship: string) => {
|
||||
if (relationship === 'parent') {
|
||||
return (
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.relationships.columnRelationship.parentAsValue"
|
||||
id="savedObjectsManagement.objectsTable.relationships.columnRelationship.parentAsValue"
|
||||
defaultMessage="Parent"
|
||||
/>
|
||||
</EuiText>
|
||||
|
@ -166,7 +173,7 @@ export class Relationships extends Component {
|
|||
return (
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.relationships.columnRelationship.childAsValue"
|
||||
id="savedObjectsManagement.objectsTable.relationships.columnRelationship.childAsValue"
|
||||
defaultMessage="Child"
|
||||
/>
|
||||
</EuiText>
|
||||
|
@ -176,17 +183,17 @@ export class Relationships extends Component {
|
|||
},
|
||||
{
|
||||
field: 'meta.title',
|
||||
name: i18n.translate('kbn.management.objects.objectsTable.relationships.columnTitleName', {
|
||||
name: i18n.translate('savedObjectsManagement.objectsTable.relationships.columnTitleName', {
|
||||
defaultMessage: 'Title',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.columnTitleDescription',
|
||||
'savedObjectsManagement.objectsTable.relationships.columnTitleDescription',
|
||||
{ defaultMessage: 'Title of the saved object' }
|
||||
),
|
||||
dataType: 'string',
|
||||
sortable: false,
|
||||
render: (title, object) => {
|
||||
const { path } = object.meta.inAppUrl || {};
|
||||
render: (title: string, object: SavedObjectWithMetadata) => {
|
||||
const { path = '' } = object.meta.inAppUrl || {};
|
||||
const canGoInApp = this.props.canGoInApp(object);
|
||||
if (!canGoInApp) {
|
||||
return (
|
||||
|
@ -196,7 +203,7 @@ export class Relationships extends Component {
|
|||
);
|
||||
}
|
||||
return (
|
||||
<EuiLink href={chrome.addBasePath(path)} data-test-subj="relationshipsTitle">
|
||||
<EuiLink href={basePath.prepend(path)} data-test-subj="relationshipsTitle">
|
||||
{title || getDefaultTitle(object)}
|
||||
</EuiLink>
|
||||
);
|
||||
|
@ -204,24 +211,24 @@ export class Relationships extends Component {
|
|||
},
|
||||
{
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.columnActionsName',
|
||||
'savedObjectsManagement.objectsTable.relationships.columnActionsName',
|
||||
{ defaultMessage: 'Actions' }
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.columnActions.inspectActionName',
|
||||
'savedObjectsManagement.objectsTable.relationships.columnActions.inspectActionName',
|
||||
{ defaultMessage: 'Inspect' }
|
||||
),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.columnActions.inspectActionDescription',
|
||||
'savedObjectsManagement.objectsTable.relationships.columnActions.inspectActionDescription',
|
||||
{ defaultMessage: 'Inspect this saved object' }
|
||||
),
|
||||
type: 'icon',
|
||||
icon: 'inspect',
|
||||
'data-test-subj': 'relationshipsTableAction-inspect',
|
||||
onClick: object => goInspectObject(object),
|
||||
available: object => !!object.meta.editUrl,
|
||||
onClick: (object: SavedObjectWithMetadata) => goInspectObject(object),
|
||||
available: (object: SavedObjectWithMetadata) => !!object.meta.editUrl,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -244,7 +251,7 @@ export class Relationships extends Component {
|
|||
type: 'field_value_selection',
|
||||
field: 'relationship',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.search.filters.relationship.name',
|
||||
'savedObjectsManagement.objectsTable.relationships.search.filters.relationship.name',
|
||||
{ defaultMessage: 'Direct relationship' }
|
||||
),
|
||||
multiSelect: 'or',
|
||||
|
@ -253,7 +260,7 @@ export class Relationships extends Component {
|
|||
value: 'parent',
|
||||
name: 'parent',
|
||||
view: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.search.filters.relationship.parentAsValue.view',
|
||||
'savedObjectsManagement.objectsTable.relationships.search.filters.relationship.parentAsValue.view',
|
||||
{ defaultMessage: 'Parent' }
|
||||
),
|
||||
},
|
||||
|
@ -261,7 +268,7 @@ export class Relationships extends Component {
|
|||
value: 'child',
|
||||
name: 'child',
|
||||
view: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.search.filters.relationship.childAsValue.view',
|
||||
'savedObjectsManagement.objectsTable.relationships.search.filters.relationship.childAsValue.view',
|
||||
{ defaultMessage: 'Child' }
|
||||
),
|
||||
},
|
||||
|
@ -271,13 +278,13 @@ export class Relationships extends Component {
|
|||
type: 'field_value_selection',
|
||||
field: 'type',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.search.filters.type.name',
|
||||
'savedObjectsManagement.objectsTable.relationships.search.filters.type.name',
|
||||
{ defaultMessage: 'Type' }
|
||||
),
|
||||
multiSelect: 'or',
|
||||
options: [...filterTypesMap.values()],
|
||||
},
|
||||
],
|
||||
] as FilterConfig[],
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -285,7 +292,7 @@ export class Relationships extends Component {
|
|||
<EuiCallOut>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'kbn.management.objects.objectsTable.relationships.relationshipsTitle',
|
||||
'savedObjectsManagement.objectsTable.relationships.relationshipsTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'Here are the saved objects related to {title}. ' +
|
||||
|
@ -301,7 +308,7 @@ export class Relationships extends Component {
|
|||
<EuiSpacer />
|
||||
<EuiInMemoryTable
|
||||
items={relationships}
|
||||
columns={columns}
|
||||
columns={columns as any}
|
||||
pagination={true}
|
||||
search={search}
|
||||
rowProps={() => ({
|
|
@ -19,27 +19,22 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers';
|
||||
// @ts-ignore
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { keyCodes } from '@elastic/eui/lib/services';
|
||||
import { npSetup as mockNpSetup } from '../../../../../../../../../../../ui/public/new_platform/__mocks__';
|
||||
import { keyCodes } from '@elastic/eui';
|
||||
import { httpServiceMock } from '../../../../../../core/public/mocks';
|
||||
import { actionServiceMock } from '../../../services/action_service.mock';
|
||||
import { Table, TableProps } from './table';
|
||||
|
||||
jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() }));
|
||||
|
||||
jest.mock('ui/chrome', () => ({
|
||||
addBasePath: () => '',
|
||||
}));
|
||||
|
||||
jest.mock('ui/new_platform', () => ({
|
||||
npSetup: mockNpSetup,
|
||||
}));
|
||||
|
||||
import { Table } from '../table';
|
||||
|
||||
const defaultProps = {
|
||||
const defaultProps: TableProps = {
|
||||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
actionRegistry: actionServiceMock.createStart(),
|
||||
selectedSavedObjects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: `MyIndexPattern*`,
|
||||
icon: 'indexPatternApp',
|
||||
|
@ -58,13 +53,15 @@ const defaultProps = {
|
|||
onDelete: () => {},
|
||||
onExport: () => {},
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => {},
|
||||
canGoInApp: () => true,
|
||||
pageIndex: 1,
|
||||
pageSize: 2,
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
attributes: {},
|
||||
references: [],
|
||||
meta: {
|
||||
title: `MyIndexPattern*`,
|
||||
icon: 'indexPatternApp',
|
||||
|
@ -120,7 +117,7 @@ describe('Table', () => {
|
|||
{ type: 'visualization' },
|
||||
{ type: 'search' },
|
||||
{ type: 'index-pattern' },
|
||||
];
|
||||
] as any;
|
||||
const customizedProps = { ...defaultProps, selectedSavedObjects, canDelete: false };
|
||||
const component = shallowWithI18nProvider(<Table {...customizedProps} />);
|
||||
|
|
@ -17,12 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import { IBasePath } from 'src/core/public';
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
// @ts-ignore
|
||||
EuiSearchBar,
|
||||
EuiBasicTable,
|
||||
EuiButton,
|
||||
|
@ -35,54 +33,64 @@ import {
|
|||
EuiSwitch,
|
||||
EuiFormRow,
|
||||
EuiText,
|
||||
EuiTableFieldDataColumnType,
|
||||
EuiTableActionsColumnType,
|
||||
} from '@elastic/eui';
|
||||
import { getDefaultTitle, getSavedObjectLabel } from '../../../../lib';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getDefaultTitle, getSavedObjectLabel } from '../../../lib';
|
||||
import { SavedObjectWithMetadata } from '../../../types';
|
||||
import {
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
SavedObjectsManagementAction,
|
||||
} from '../../../services';
|
||||
|
||||
export class Table extends PureComponent {
|
||||
static propTypes = {
|
||||
selectedSavedObjects: PropTypes.array.isRequired,
|
||||
selectionConfig: PropTypes.shape({
|
||||
selectable: PropTypes.func,
|
||||
selectableMessage: PropTypes.func,
|
||||
onSelectionChange: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
filterOptions: PropTypes.array.isRequired,
|
||||
canDelete: PropTypes.bool.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onExport: PropTypes.func.isRequired,
|
||||
goInspectObject: PropTypes.func.isRequired,
|
||||
|
||||
pageIndex: PropTypes.number.isRequired,
|
||||
pageSize: PropTypes.number.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
itemId: PropTypes.oneOfType([
|
||||
PropTypes.string, // the name of the item id property
|
||||
PropTypes.func, // (item) => string
|
||||
]),
|
||||
totalItemCount: PropTypes.number.isRequired,
|
||||
onQueryChange: PropTypes.func.isRequired,
|
||||
onTableChange: PropTypes.func.isRequired,
|
||||
isSearching: PropTypes.bool.isRequired,
|
||||
|
||||
onShowRelationships: PropTypes.func.isRequired,
|
||||
export interface TableProps {
|
||||
basePath: IBasePath;
|
||||
actionRegistry: SavedObjectsManagementActionServiceStart;
|
||||
selectedSavedObjects: SavedObjectWithMetadata[];
|
||||
selectionConfig: {
|
||||
onSelectionChange: (selection: SavedObjectWithMetadata[]) => void;
|
||||
};
|
||||
filterOptions: any[];
|
||||
canDelete: boolean;
|
||||
onDelete: () => void;
|
||||
onExport: (includeReferencesDeep: boolean) => void;
|
||||
goInspectObject: (obj: SavedObjectWithMetadata) => void;
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
items: SavedObjectWithMetadata[];
|
||||
itemId: string | (() => string);
|
||||
totalItemCount: number;
|
||||
onQueryChange: (query: any) => void;
|
||||
onTableChange: (table: any) => void;
|
||||
isSearching: boolean;
|
||||
onShowRelationships: (object: SavedObjectWithMetadata) => void;
|
||||
canGoInApp: (obj: SavedObjectWithMetadata) => boolean;
|
||||
}
|
||||
|
||||
state = {
|
||||
interface TableState {
|
||||
isSearchTextValid: boolean;
|
||||
parseErrorMessage: any;
|
||||
isExportPopoverOpen: boolean;
|
||||
isIncludeReferencesDeepChecked: boolean;
|
||||
activeAction?: SavedObjectsManagementAction;
|
||||
}
|
||||
|
||||
export class Table extends PureComponent<TableProps, TableState> {
|
||||
state: TableState = {
|
||||
isSearchTextValid: true,
|
||||
parseErrorMessage: null,
|
||||
isExportPopoverOpen: false,
|
||||
isIncludeReferencesDeepChecked: true,
|
||||
activeAction: null,
|
||||
activeAction: undefined,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: TableProps) {
|
||||
super(props);
|
||||
this.extraActions = npSetup.plugins.savedObjectsManagement.actionRegistry.getAll();
|
||||
}
|
||||
|
||||
onChange = ({ query, error }) => {
|
||||
onChange = ({ query, error }: any) => {
|
||||
if (error) {
|
||||
this.setState({
|
||||
isSearchTextValid: false,
|
||||
|
@ -136,12 +144,14 @@ export class Table extends PureComponent {
|
|||
onTableChange,
|
||||
goInspectObject,
|
||||
onShowRelationships,
|
||||
basePath,
|
||||
actionRegistry,
|
||||
} = this.props;
|
||||
|
||||
const pagination = {
|
||||
pageIndex: pageIndex,
|
||||
pageSize: pageSize,
|
||||
totalItemCount: totalItemCount,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount,
|
||||
pageSizeOptions: [5, 10, 20, 50],
|
||||
};
|
||||
|
||||
|
@ -149,7 +159,7 @@ export class Table extends PureComponent {
|
|||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'type',
|
||||
name: i18n.translate('kbn.management.objects.objectsTable.table.typeFilterName', {
|
||||
name: i18n.translate('savedObjectsManagement.objectsTable.table.typeFilterName', {
|
||||
defaultMessage: 'Type',
|
||||
}),
|
||||
multiSelect: 'or',
|
||||
|
@ -168,18 +178,18 @@ export class Table extends PureComponent {
|
|||
const columns = [
|
||||
{
|
||||
field: 'type',
|
||||
name: i18n.translate('kbn.management.objects.objectsTable.table.columnTypeName', {
|
||||
name: i18n.translate('savedObjectsManagement.objectsTable.table.columnTypeName', {
|
||||
defaultMessage: 'Type',
|
||||
}),
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.table.columnTypeDescription',
|
||||
'savedObjectsManagement.objectsTable.table.columnTypeDescription',
|
||||
{ defaultMessage: 'Type of the saved object' }
|
||||
),
|
||||
sortable: false,
|
||||
'data-test-subj': 'savedObjectsTableRowType',
|
||||
render: (type, object) => {
|
||||
render: (type: string, object: SavedObjectWithMetadata) => {
|
||||
return (
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
|
||||
<EuiIcon
|
||||
|
@ -191,42 +201,42 @@ export class Table extends PureComponent {
|
|||
</EuiToolTip>
|
||||
);
|
||||
},
|
||||
},
|
||||
} as EuiTableFieldDataColumnType<SavedObjectWithMetadata<any>>,
|
||||
{
|
||||
field: 'meta.title',
|
||||
name: i18n.translate('kbn.management.objects.objectsTable.table.columnTitleName', {
|
||||
name: i18n.translate('savedObjectsManagement.objectsTable.table.columnTitleName', {
|
||||
defaultMessage: 'Title',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.table.columnTitleDescription',
|
||||
'savedObjectsManagement.objectsTable.table.columnTitleDescription',
|
||||
{ defaultMessage: 'Title of the saved object' }
|
||||
),
|
||||
dataType: 'string',
|
||||
sortable: false,
|
||||
'data-test-subj': 'savedObjectsTableRowTitle',
|
||||
render: (title, object) => {
|
||||
const { path } = object.meta.inAppUrl || {};
|
||||
render: (title: string, object: SavedObjectWithMetadata) => {
|
||||
const { path = '' } = object.meta.inAppUrl || {};
|
||||
const canGoInApp = this.props.canGoInApp(object);
|
||||
if (!canGoInApp) {
|
||||
return <EuiText size="s">{title || getDefaultTitle(object)}</EuiText>;
|
||||
}
|
||||
return (
|
||||
<EuiLink href={chrome.addBasePath(path)}>{title || getDefaultTitle(object)}</EuiLink>
|
||||
<EuiLink href={basePath.prepend(path)}>{title || getDefaultTitle(object)}</EuiLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
} as EuiTableFieldDataColumnType<SavedObjectWithMetadata<any>>,
|
||||
{
|
||||
name: i18n.translate('kbn.management.objects.objectsTable.table.columnActionsName', {
|
||||
name: i18n.translate('savedObjectsManagement.objectsTable.table.columnActionsName', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
actions: [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.table.columnActions.inspectActionName',
|
||||
'savedObjectsManagement.objectsTable.table.columnActions.inspectActionName',
|
||||
{ defaultMessage: 'Inspect' }
|
||||
),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.table.columnActions.inspectActionDescription',
|
||||
'savedObjectsManagement.objectsTable.table.columnActions.inspectActionDescription',
|
||||
{ defaultMessage: 'Inspect this saved object' }
|
||||
),
|
||||
type: 'icon',
|
||||
|
@ -237,11 +247,11 @@ export class Table extends PureComponent {
|
|||
},
|
||||
{
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionName',
|
||||
'savedObjectsManagement.objectsTable.table.columnActions.viewRelationshipsActionName',
|
||||
{ defaultMessage: 'Relationships' }
|
||||
),
|
||||
description: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionDescription',
|
||||
'savedObjectsManagement.objectsTable.table.columnActions.viewRelationshipsActionDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'View the relationships this saved object has to other saved objects',
|
||||
|
@ -252,33 +262,35 @@ export class Table extends PureComponent {
|
|||
onClick: object => onShowRelationships(object),
|
||||
'data-test-subj': 'savedObjectsTableAction-relationships',
|
||||
},
|
||||
...this.extraActions.map(action => {
|
||||
...actionRegistry.getAll().map(action => {
|
||||
return {
|
||||
...action.euiAction,
|
||||
'data-test-subj': `savedObjectsTableAction-${action.id}`,
|
||||
onClick: object => {
|
||||
onClick: (object: SavedObjectWithMetadata) => {
|
||||
this.setState({
|
||||
activeAction: action,
|
||||
});
|
||||
|
||||
action.registerOnFinishCallback(() => {
|
||||
this.setState({
|
||||
activeAction: null,
|
||||
activeAction: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
action.euiAction.onClick(object);
|
||||
if (action.euiAction.onClick) {
|
||||
action.euiAction.onClick(object as any);
|
||||
}
|
||||
},
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
} as EuiTableActionsColumnType<SavedObjectWithMetadata>,
|
||||
];
|
||||
|
||||
let queryParseError;
|
||||
if (!this.state.isSearchTextValid) {
|
||||
const parseErrorMsg = i18n.translate(
|
||||
'kbn.management.objects.objectsTable.searchBar.unableToParseQueryErrorMessage',
|
||||
'savedObjectsManagement.objectsTable.searchBar.unableToParseQueryErrorMessage',
|
||||
{ defaultMessage: 'Unable to parse query' }
|
||||
);
|
||||
queryParseError = (
|
||||
|
@ -294,20 +306,20 @@ export class Table extends PureComponent {
|
|||
isDisabled={selectedSavedObjects.length === 0}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.table.exportPopoverButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.exportPopoverButtonLabel"
|
||||
defaultMessage="Export"
|
||||
/>
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
const activeActionContents = this.state.activeAction ? this.state.activeAction.render() : null;
|
||||
const activeActionContents = this.state.activeAction?.render() ?? null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{activeActionContents}
|
||||
<EuiSearchBar
|
||||
box={{ 'data-test-subj': 'savedObjectSearchBar' }}
|
||||
filters={filters}
|
||||
filters={filters as any}
|
||||
onChange={this.onChange}
|
||||
toolsRight={[
|
||||
<EuiButton
|
||||
|
@ -319,14 +331,14 @@ export class Table extends PureComponent {
|
|||
title={
|
||||
this.props.canDelete
|
||||
? undefined
|
||||
: i18n.translate('kbn.management.objects.objectsTable.table.deleteButtonTitle', {
|
||||
: i18n.translate('savedObjectsManagement.objectsTable.table.deleteButtonTitle', {
|
||||
defaultMessage: 'Unable to delete saved objects',
|
||||
})
|
||||
}
|
||||
data-test-subj="savedObjectsManagementDelete"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.table.deleteButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.deleteButtonLabel"
|
||||
defaultMessage="Delete"
|
||||
/>
|
||||
</EuiButton>,
|
||||
|
@ -339,7 +351,7 @@ export class Table extends PureComponent {
|
|||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.exportOptionsLabel"
|
||||
defaultMessage="Options"
|
||||
/>
|
||||
}
|
||||
|
@ -348,7 +360,7 @@ export class Table extends PureComponent {
|
|||
name="includeReferencesDeep"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
defaultMessage="Include related objects"
|
||||
/>
|
||||
}
|
||||
|
@ -359,7 +371,7 @@ export class Table extends PureComponent {
|
|||
<EuiFormRow>
|
||||
<EuiButton key="exportSO" iconType="exportAction" onClick={this.onExportClick} fill>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.table.exportButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.table.exportButtonLabel"
|
||||
defaultMessage="Export"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -374,7 +386,7 @@ export class Table extends PureComponent {
|
|||
loading={isSearching}
|
||||
itemId={itemId}
|
||||
items={items}
|
||||
columns={columns}
|
||||
columns={columns as any}
|
||||
pagination={pagination}
|
||||
selection={selection}
|
||||
onChange={onTableChange}
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Table } from './table';
|
||||
export { SavedObjectsTable } from './saved_objects_table';
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export const saveAsMock = jest.fn();
|
||||
jest.doMock('@elastic/filesaver', () => ({
|
||||
saveAs: saveAsMock,
|
||||
}));
|
||||
|
||||
jest.doMock('lodash', () => ({
|
||||
...jest.requireActual('lodash'),
|
||||
debounce: (func: Function) => {
|
||||
function debounced(this: any, ...args: any[]) {
|
||||
return func.apply(this, args);
|
||||
}
|
||||
return debounced;
|
||||
},
|
||||
}));
|
||||
|
||||
export const findObjectsMock = jest.fn();
|
||||
jest.doMock('../../lib/find_objects', () => ({
|
||||
findObjects: findObjectsMock,
|
||||
}));
|
||||
|
||||
export const fetchExportObjectsMock = jest.fn();
|
||||
jest.doMock('../../lib/fetch_export_objects', () => ({
|
||||
fetchExportObjects: fetchExportObjectsMock,
|
||||
}));
|
||||
|
||||
export const fetchExportByTypeAndSearchMock = jest.fn();
|
||||
jest.doMock('../../lib/fetch_export_by_type_and_search', () => ({
|
||||
fetchExportByTypeAndSearch: fetchExportByTypeAndSearchMock,
|
||||
}));
|
||||
|
||||
export const extractExportDetailsMock = jest.fn();
|
||||
jest.doMock('../../lib/extract_export_details', () => ({
|
||||
extractExportDetails: extractExportDetailsMock,
|
||||
}));
|
||||
|
||||
jest.doMock('./components/header', () => ({
|
||||
Header: () => 'Header',
|
||||
}));
|
||||
|
||||
export const getSavedObjectCountsMock = jest.fn();
|
||||
jest.doMock('../../lib/get_saved_object_counts', () => ({
|
||||
getSavedObjectCounts: getSavedObjectCountsMock,
|
||||
}));
|
||||
|
||||
export const getRelationshipsMock = jest.fn();
|
||||
jest.doMock('../../lib/get_relationships', () => ({
|
||||
getRelationships: getRelationshipsMock,
|
||||
}));
|
|
@ -17,69 +17,39 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
extractExportDetailsMock,
|
||||
fetchExportByTypeAndSearchMock,
|
||||
fetchExportObjectsMock,
|
||||
findObjectsMock,
|
||||
getRelationshipsMock,
|
||||
getSavedObjectCountsMock,
|
||||
saveAsMock,
|
||||
} from './saved_objects_table.test.mocks';
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers';
|
||||
import { mockManagementPlugin } from '../../../../../../../../../../plugins/index_pattern_management/public/mocks';
|
||||
import { Query } from '@elastic/eui';
|
||||
import { ShallowWrapper } from 'enzyme';
|
||||
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers';
|
||||
import {
|
||||
httpServiceMock,
|
||||
overlayServiceMock,
|
||||
notificationServiceMock,
|
||||
savedObjectsServiceMock,
|
||||
applicationServiceMock,
|
||||
} from '../../../../../core/public/mocks';
|
||||
import { dataPluginMock } from '../../../../data/public/mocks';
|
||||
import { serviceRegistryMock } from '../../services/service_registry.mock';
|
||||
import { actionServiceMock } from '../../services/action_service.mock';
|
||||
import {
|
||||
SavedObjectsTable,
|
||||
SavedObjectsTableProps,
|
||||
SavedObjectsTableState,
|
||||
} from './saved_objects_table';
|
||||
import { Flyout, Relationships } from './components';
|
||||
import { SavedObjectWithMetadata } from '../../types';
|
||||
|
||||
import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table';
|
||||
import { Flyout } from '../components/flyout/';
|
||||
import { Relationships } from '../components/relationships/';
|
||||
import { findObjects } from '../../../lib';
|
||||
import { extractExportDetails } from '../../../lib/extract_export_details';
|
||||
|
||||
jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() }));
|
||||
|
||||
jest.mock('../../../../../../../../../../plugins/index_pattern_management/public', () => ({
|
||||
setup: mockManagementPlugin.createSetupContract(),
|
||||
start: mockManagementPlugin.createStartContract(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/find_objects', () => ({
|
||||
findObjects: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../components/header', () => ({
|
||||
Header: () => 'Header',
|
||||
}));
|
||||
|
||||
jest.mock('ui/chrome', () => ({
|
||||
addBasePath: () => '',
|
||||
getInjected: () => ['index-pattern', 'visualization', 'dashboard', 'search'],
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/fetch_export_objects', () => ({
|
||||
fetchExportObjects: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({
|
||||
fetchExportByTypeAndSearch: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/extract_export_details', () => ({
|
||||
extractExportDetails: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/get_saved_object_counts', () => ({
|
||||
getSavedObjectCounts: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
'index-pattern': 0,
|
||||
visualization: 0,
|
||||
dashboard: 0,
|
||||
search: 0,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@elastic/filesaver', () => ({
|
||||
saveAs: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/get_relationships', () => ({
|
||||
getRelationships: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ui/notify', () => ({}));
|
||||
const allowedTypes = ['index-pattern', 'visualization', 'dashboard', 'search'];
|
||||
|
||||
const allSavedObjects = [
|
||||
{
|
||||
|
@ -112,122 +82,128 @@ const allSavedObjects = [
|
|||
},
|
||||
];
|
||||
|
||||
const $http = () => {};
|
||||
$http.post = jest.fn().mockImplementation(() => []);
|
||||
const defaultProps = {
|
||||
goInspectObject: () => {},
|
||||
confirmModalPromise: jest.fn(),
|
||||
savedObjectsClient: {
|
||||
find: jest.fn(),
|
||||
bulkGet: jest.fn(),
|
||||
},
|
||||
indexPatterns: {
|
||||
clearCache: jest.fn(),
|
||||
},
|
||||
$http,
|
||||
basePath: '',
|
||||
newIndexPatternUrl: '',
|
||||
kbnIndex: '',
|
||||
services: [],
|
||||
uiCapabilities: {
|
||||
savedObjectsManagement: {
|
||||
read: true,
|
||||
edit: false,
|
||||
delete: false,
|
||||
},
|
||||
},
|
||||
canDelete: true,
|
||||
};
|
||||
describe('SavedObjectsTable', () => {
|
||||
let defaultProps: SavedObjectsTableProps;
|
||||
let http: ReturnType<typeof httpServiceMock.createStartContract>;
|
||||
let overlays: ReturnType<typeof overlayServiceMock.createStartContract>;
|
||||
let notifications: ReturnType<typeof notificationServiceMock.createStartContract>;
|
||||
let savedObjects: ReturnType<typeof savedObjectsServiceMock.createStartContract>;
|
||||
|
||||
beforeEach(() => {
|
||||
findObjects.mockImplementation(() => ({
|
||||
total: 4,
|
||||
savedObjects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
meta: {
|
||||
title: `MyIndexPattern*`,
|
||||
icon: 'indexPatternApp',
|
||||
editUrl: '#/management/kibana/index_patterns/1',
|
||||
inAppUrl: {
|
||||
path: '/management/kibana/index_patterns/1',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'search',
|
||||
meta: {
|
||||
title: `MySearch`,
|
||||
icon: 'search',
|
||||
editUrl: '#/management/kibana/objects/savedSearches/2',
|
||||
inAppUrl: {
|
||||
path: '/discover/2',
|
||||
uiCapabilitiesPath: 'discover.show',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
title: `MyDashboard`,
|
||||
icon: 'dashboardApp',
|
||||
editUrl: '#/management/kibana/objects/savedDashboards/3',
|
||||
inAppUrl: {
|
||||
path: '/dashboard/3',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'visualization',
|
||||
meta: {
|
||||
title: `MyViz`,
|
||||
icon: 'visualizeApp',
|
||||
editUrl: '#/management/kibana/objects/savedVisualizations/4',
|
||||
inAppUrl: {
|
||||
path: '/visualize/edit/4',
|
||||
uiCapabilitiesPath: 'visualize.show',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
});
|
||||
const shallowRender = (overrides: Partial<SavedObjectsTableProps> = {}) => {
|
||||
return (shallowWithI18nProvider(
|
||||
<SavedObjectsTable {...defaultProps} {...overrides} />
|
||||
) as unknown) as ShallowWrapper<
|
||||
SavedObjectsTableProps,
|
||||
SavedObjectsTableState,
|
||||
SavedObjectsTable
|
||||
>;
|
||||
};
|
||||
|
||||
let addDangerMock;
|
||||
let addSuccessMock;
|
||||
let addWarningMock;
|
||||
|
||||
describe('ObjectsTable', () => {
|
||||
beforeEach(() => {
|
||||
defaultProps.savedObjectsClient.find.mockClear();
|
||||
extractExportDetails.mockReset();
|
||||
// mock _.debounce to fire immediately with no internal timer
|
||||
require('lodash').debounce = func => {
|
||||
function debounced(...args) {
|
||||
return func.apply(this, args);
|
||||
}
|
||||
return debounced;
|
||||
extractExportDetailsMock.mockReset();
|
||||
|
||||
http = httpServiceMock.createStartContract();
|
||||
overlays = overlayServiceMock.createStartContract();
|
||||
notifications = notificationServiceMock.createStartContract();
|
||||
savedObjects = savedObjectsServiceMock.createStartContract();
|
||||
|
||||
const applications = applicationServiceMock.createStartContract();
|
||||
applications.capabilities = {
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
savedObjectsManagement: {
|
||||
read: true,
|
||||
edit: false,
|
||||
delete: false,
|
||||
},
|
||||
};
|
||||
addDangerMock = jest.fn();
|
||||
addSuccessMock = jest.fn();
|
||||
addWarningMock = jest.fn();
|
||||
require('ui/notify').toastNotifications = {
|
||||
addDanger: addDangerMock,
|
||||
addSuccess: addSuccessMock,
|
||||
addWarning: addWarningMock,
|
||||
|
||||
http.post.mockResolvedValue([]);
|
||||
|
||||
getSavedObjectCountsMock.mockReturnValue({
|
||||
'index-pattern': 0,
|
||||
visualization: 0,
|
||||
dashboard: 0,
|
||||
search: 0,
|
||||
});
|
||||
|
||||
defaultProps = {
|
||||
allowedTypes,
|
||||
serviceRegistry: serviceRegistryMock.create(),
|
||||
actionRegistry: actionServiceMock.createStart(),
|
||||
savedObjectsClient: savedObjects.client,
|
||||
indexPatterns: dataPluginMock.createStartContract().indexPatterns,
|
||||
http,
|
||||
overlays,
|
||||
notifications,
|
||||
applications,
|
||||
perPageConfig: 15,
|
||||
goInspectObject: () => {},
|
||||
canGoInApp: () => true,
|
||||
};
|
||||
|
||||
findObjectsMock.mockImplementation(() => ({
|
||||
total: 4,
|
||||
savedObjects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
meta: {
|
||||
title: `MyIndexPattern*`,
|
||||
icon: 'indexPatternApp',
|
||||
editUrl: '#/management/kibana/index_patterns/1',
|
||||
inAppUrl: {
|
||||
path: '/management/kibana/index_patterns/1',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'search',
|
||||
meta: {
|
||||
title: `MySearch`,
|
||||
icon: 'search',
|
||||
editUrl: '#/management/kibana/objects/savedSearches/2',
|
||||
inAppUrl: {
|
||||
path: '/discover/2',
|
||||
uiCapabilitiesPath: 'discover.show',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
title: `MyDashboard`,
|
||||
icon: 'dashboardApp',
|
||||
editUrl: '#/management/kibana/objects/savedDashboards/3',
|
||||
inAppUrl: {
|
||||
path: '/dashboard/3',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'visualization',
|
||||
meta: {
|
||||
title: `MyViz`,
|
||||
icon: 'visualizeApp',
|
||||
editUrl: '#/management/kibana/objects/savedVisualizations/4',
|
||||
inAppUrl: {
|
||||
path: '/visualize/edit/4',
|
||||
uiCapabilitiesPath: 'visualize.show',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
||||
it('should render normally', async () => {
|
||||
const component = shallowWithI18nProvider(
|
||||
<ObjectsTable {...defaultProps} perPageConfig={15} />
|
||||
);
|
||||
const component = shallowRender({ perPageConfig: 15 });
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -238,19 +214,17 @@ describe('ObjectsTable', () => {
|
|||
});
|
||||
|
||||
it('should add danger toast when find fails', async () => {
|
||||
findObjects.mockImplementation(() => {
|
||||
findObjectsMock.mockImplementation(() => {
|
||||
throw new Error('Simulated find error');
|
||||
});
|
||||
const component = shallowWithI18nProvider(
|
||||
<ObjectsTable {...defaultProps} perPageConfig={15} />
|
||||
);
|
||||
const component = shallowRender({ perPageConfig: 15 });
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
expect(addDangerMock).toHaveBeenCalled();
|
||||
expect(notifications.toasts.addDanger).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('export', () => {
|
||||
|
@ -258,7 +232,7 @@ describe('ObjectsTable', () => {
|
|||
const mockSelectedSavedObjects = [
|
||||
{ id: '1', type: 'index-pattern' },
|
||||
{ id: '3', type: 'dashboard' },
|
||||
];
|
||||
] as SavedObjectWithMetadata[];
|
||||
|
||||
const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({
|
||||
_id: obj.id,
|
||||
|
@ -272,11 +246,7 @@ describe('ObjectsTable', () => {
|
|||
})),
|
||||
};
|
||||
|
||||
const { fetchExportObjects } = require('../../../lib/fetch_export_objects');
|
||||
|
||||
const component = shallowWithI18nProvider(
|
||||
<ObjectsTable {...defaultProps} savedObjectsClient={mockSavedObjectsClient} />
|
||||
);
|
||||
const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient });
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -288,8 +258,8 @@ describe('ObjectsTable', () => {
|
|||
|
||||
await component.instance().onExport(true);
|
||||
|
||||
expect(fetchExportObjects).toHaveBeenCalledWith(mockSelectedSavedObjects, true);
|
||||
expect(addSuccessMock).toHaveBeenCalledWith({
|
||||
expect(fetchExportObjectsMock).toHaveBeenCalledWith(http, mockSelectedSavedObjects, true);
|
||||
expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
title: 'Your file is downloading in the background',
|
||||
});
|
||||
});
|
||||
|
@ -298,7 +268,7 @@ describe('ObjectsTable', () => {
|
|||
const mockSelectedSavedObjects = [
|
||||
{ id: '1', type: 'index-pattern' },
|
||||
{ id: '3', type: 'dashboard' },
|
||||
];
|
||||
] as SavedObjectWithMetadata[];
|
||||
|
||||
const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({
|
||||
_id: obj.id,
|
||||
|
@ -312,16 +282,13 @@ describe('ObjectsTable', () => {
|
|||
})),
|
||||
};
|
||||
|
||||
const { fetchExportObjects } = require('../../../lib/fetch_export_objects');
|
||||
extractExportDetails.mockImplementation(() => ({
|
||||
extractExportDetailsMock.mockImplementation(() => ({
|
||||
exportedCount: 2,
|
||||
missingRefCount: 1,
|
||||
missingReferences: [{ id: '7', type: 'visualisation' }],
|
||||
}));
|
||||
|
||||
const component = shallowWithI18nProvider(
|
||||
<ObjectsTable {...defaultProps} savedObjectsClient={mockSavedObjectsClient} />
|
||||
);
|
||||
const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient });
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -333,8 +300,8 @@ describe('ObjectsTable', () => {
|
|||
|
||||
await component.instance().onExport(true);
|
||||
|
||||
expect(fetchExportObjects).toHaveBeenCalledWith(mockSelectedSavedObjects, true);
|
||||
expect(addWarningMock).toHaveBeenCalledWith({
|
||||
expect(fetchExportObjectsMock).toHaveBeenCalledWith(http, mockSelectedSavedObjects, true);
|
||||
expect(notifications.toasts.addWarning).toHaveBeenCalledWith({
|
||||
title:
|
||||
'Your file is downloading in the background. ' +
|
||||
'Some related objects could not be found. ' +
|
||||
|
@ -343,25 +310,21 @@ describe('ObjectsTable', () => {
|
|||
});
|
||||
|
||||
it('should allow the user to choose when exporting all', async () => {
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
component.find('Header').prop('onExportAll')();
|
||||
(component.find('Header') as any).prop('onExportAll')();
|
||||
component.update();
|
||||
|
||||
expect(component.find('EuiModal')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should export all', async () => {
|
||||
const {
|
||||
fetchExportByTypeAndSearch,
|
||||
} = require('../../../lib/fetch_export_by_type_and_search');
|
||||
const { saveAs } = require('@elastic/filesaver');
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -370,23 +333,24 @@ describe('ObjectsTable', () => {
|
|||
|
||||
// Set up mocks
|
||||
const blob = new Blob([JSON.stringify(allSavedObjects)], { type: 'application/ndjson' });
|
||||
fetchExportByTypeAndSearch.mockImplementation(() => blob);
|
||||
fetchExportByTypeAndSearchMock.mockImplementation(() => blob);
|
||||
|
||||
await component.instance().onExportAll();
|
||||
|
||||
expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, undefined, true);
|
||||
expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson');
|
||||
expect(addSuccessMock).toHaveBeenCalledWith({
|
||||
expect(fetchExportByTypeAndSearchMock).toHaveBeenCalledWith(
|
||||
http,
|
||||
allowedTypes,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
expect(saveAsMock).toHaveBeenCalledWith(blob, 'export.ndjson');
|
||||
expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
title: 'Your file is downloading in the background',
|
||||
});
|
||||
});
|
||||
|
||||
it('should export all, accounting for the current search criteria', async () => {
|
||||
const {
|
||||
fetchExportByTypeAndSearch,
|
||||
} = require('../../../lib/fetch_export_by_type_and_search');
|
||||
const { saveAs } = require('@elastic/filesaver');
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
component.instance().onQueryChange({
|
||||
query: Query.parse('test'),
|
||||
|
@ -399,13 +363,18 @@ describe('ObjectsTable', () => {
|
|||
|
||||
// Set up mocks
|
||||
const blob = new Blob([JSON.stringify(allSavedObjects)], { type: 'application/ndjson' });
|
||||
fetchExportByTypeAndSearch.mockImplementation(() => blob);
|
||||
fetchExportByTypeAndSearchMock.mockImplementation(() => blob);
|
||||
|
||||
await component.instance().onExportAll();
|
||||
|
||||
expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, 'test*', true);
|
||||
expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson');
|
||||
expect(addSuccessMock).toHaveBeenCalledWith({
|
||||
expect(fetchExportByTypeAndSearchMock).toHaveBeenCalledWith(
|
||||
http,
|
||||
allowedTypes,
|
||||
'test*',
|
||||
true
|
||||
);
|
||||
expect(saveAsMock).toHaveBeenCalledWith(blob, 'export.ndjson');
|
||||
expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
title: 'Your file is downloading in the background',
|
||||
});
|
||||
});
|
||||
|
@ -413,7 +382,7 @@ describe('ObjectsTable', () => {
|
|||
|
||||
describe('import', () => {
|
||||
it('should show the flyout', async () => {
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -427,7 +396,7 @@ describe('ObjectsTable', () => {
|
|||
});
|
||||
|
||||
it('should hide the flyout', async () => {
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -443,9 +412,7 @@ describe('ObjectsTable', () => {
|
|||
|
||||
describe('relationships', () => {
|
||||
it('should fetch relationships', async () => {
|
||||
const { getRelationships } = require('../../../lib/get_relationships');
|
||||
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -454,17 +421,11 @@ describe('ObjectsTable', () => {
|
|||
|
||||
await component.instance().getRelationships('search', '1');
|
||||
const savedObjectTypes = ['index-pattern', 'visualization', 'dashboard', 'search'];
|
||||
expect(getRelationships).toHaveBeenCalledWith(
|
||||
'search',
|
||||
'1',
|
||||
savedObjectTypes,
|
||||
defaultProps.$http,
|
||||
defaultProps.basePath
|
||||
);
|
||||
expect(getRelationshipsMock).toHaveBeenCalledWith(http, 'search', '1', savedObjectTypes);
|
||||
});
|
||||
|
||||
it('should show the flyout', async () => {
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -483,7 +444,7 @@ describe('ObjectsTable', () => {
|
|||
uiCapabilitiesPath: 'discover.show',
|
||||
},
|
||||
},
|
||||
});
|
||||
} as SavedObjectWithMetadata);
|
||||
component.update();
|
||||
|
||||
expect(component.find(Relationships)).toMatchSnapshot();
|
||||
|
@ -503,7 +464,7 @@ describe('ObjectsTable', () => {
|
|||
});
|
||||
|
||||
it('should hide the flyout', async () => {
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -522,12 +483,12 @@ describe('ObjectsTable', () => {
|
|||
|
||||
describe('delete', () => {
|
||||
it('should show a confirm modal', async () => {
|
||||
const component = shallowWithI18nProvider(<ObjectsTable {...defaultProps} />);
|
||||
const component = shallowRender();
|
||||
|
||||
const mockSelectedSavedObjects = [
|
||||
{ id: '1', type: 'index-pattern', title: 'Title 1' },
|
||||
{ id: '3', type: 'dashboard', title: 'Title 2' },
|
||||
];
|
||||
{ id: '1', type: 'index-pattern' },
|
||||
{ id: '3', type: 'dashboard' },
|
||||
] as SavedObjectWithMetadata[];
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
@ -546,7 +507,7 @@ describe('ObjectsTable', () => {
|
|||
const mockSelectedSavedObjects = [
|
||||
{ id: '1', type: 'index-pattern' },
|
||||
{ id: '3', type: 'dashboard' },
|
||||
];
|
||||
] as SavedObjectWithMetadata[];
|
||||
|
||||
const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({
|
||||
id: obj.id,
|
||||
|
@ -562,9 +523,7 @@ describe('ObjectsTable', () => {
|
|||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
const component = shallowWithI18nProvider(
|
||||
<ObjectsTable {...defaultProps} savedObjectsClient={mockSavedObjectsClient} />
|
||||
);
|
||||
const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient });
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
|
@ -17,17 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { saveAs } from '@elastic/filesaver';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'lodash';
|
||||
import { Header } from './components/header';
|
||||
import { Flyout } from './components/flyout';
|
||||
import { Relationships } from './components/relationships';
|
||||
import { Table } from './components/table';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
// @ts-ignore
|
||||
import { saveAs } from '@elastic/filesaver';
|
||||
import {
|
||||
EuiSpacer,
|
||||
Query,
|
||||
|
@ -54,7 +47,15 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsFindOptions,
|
||||
HttpStart,
|
||||
OverlayStart,
|
||||
NotificationsStart,
|
||||
ApplicationStart,
|
||||
} from 'src/core/public';
|
||||
import { IndexPatternsContract } from '../../../../data/public';
|
||||
import {
|
||||
parseQuery,
|
||||
getSavedObjectCounts,
|
||||
|
@ -63,39 +64,72 @@ import {
|
|||
fetchExportObjects,
|
||||
fetchExportByTypeAndSearch,
|
||||
findObjects,
|
||||
extractExportDetails,
|
||||
SavedObjectsExportResultDetails,
|
||||
} from '../../lib';
|
||||
import { extractExportDetails } from '../../lib/extract_export_details';
|
||||
import { SavedObjectWithMetadata } from '../../types';
|
||||
import {
|
||||
ISavedObjectsManagementServiceRegistry,
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
} from '../../services';
|
||||
import { Header, Table, Flyout, Relationships } from './components';
|
||||
|
||||
export const POSSIBLE_TYPES = chrome.getInjected('importAndExportableTypes');
|
||||
interface ExportAllOption {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export class ObjectsTable extends Component {
|
||||
static propTypes = {
|
||||
savedObjectsClient: PropTypes.object.isRequired,
|
||||
indexPatterns: PropTypes.object.isRequired,
|
||||
$http: PropTypes.func.isRequired,
|
||||
basePath: PropTypes.string.isRequired,
|
||||
perPageConfig: PropTypes.number,
|
||||
newIndexPatternUrl: PropTypes.string.isRequired,
|
||||
confirmModalPromise: PropTypes.func.isRequired,
|
||||
services: PropTypes.array.isRequired,
|
||||
uiCapabilities: PropTypes.object.isRequired,
|
||||
goInspectObject: PropTypes.func.isRequired,
|
||||
canGoInApp: PropTypes.func.isRequired,
|
||||
};
|
||||
export interface SavedObjectsTableProps {
|
||||
allowedTypes: string[];
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
actionRegistry: SavedObjectsManagementActionServiceStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
indexPatterns: IndexPatternsContract;
|
||||
http: HttpStart;
|
||||
overlays: OverlayStart;
|
||||
notifications: NotificationsStart;
|
||||
applications: ApplicationStart;
|
||||
perPageConfig: number;
|
||||
goInspectObject: (obj: SavedObjectWithMetadata) => void;
|
||||
canGoInApp: (obj: SavedObjectWithMetadata) => boolean;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
export interface SavedObjectsTableState {
|
||||
totalCount: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
savedObjects: SavedObjectWithMetadata[];
|
||||
savedObjectCounts: Record<string, number>;
|
||||
activeQuery: Query;
|
||||
selectedSavedObjects: SavedObjectWithMetadata[];
|
||||
isShowingImportFlyout: boolean;
|
||||
isSearching: boolean;
|
||||
filteredItemCount: number;
|
||||
isShowingRelationships: boolean;
|
||||
relationshipObject?: SavedObjectWithMetadata;
|
||||
isShowingDeleteConfirmModal: boolean;
|
||||
isShowingExportAllOptionsModal: boolean;
|
||||
isDeleting: boolean;
|
||||
exportAllOptions: ExportAllOption[];
|
||||
exportAllSelectedOptions: Record<string, boolean>;
|
||||
isIncludeReferencesDeepChecked: boolean;
|
||||
}
|
||||
|
||||
export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedObjectsTableState> {
|
||||
private _isMounted = false;
|
||||
|
||||
constructor(props: SavedObjectsTableProps) {
|
||||
super(props);
|
||||
this.savedObjectTypes = POSSIBLE_TYPES;
|
||||
|
||||
this.state = {
|
||||
totalCount: 0,
|
||||
page: 0,
|
||||
perPage: props.perPageConfig || 50,
|
||||
savedObjects: [],
|
||||
savedObjectCounts: this.savedObjectTypes.reduce((typeToCountMap, type) => {
|
||||
savedObjectCounts: props.allowedTypes.reduce((typeToCountMap, type) => {
|
||||
typeToCountMap[type] = 0;
|
||||
return typeToCountMap;
|
||||
}, {}),
|
||||
}, {} as Record<string, number>),
|
||||
activeQuery: Query.parse(''),
|
||||
selectedSavedObjects: [],
|
||||
isShowingImportFlyout: false,
|
||||
|
@ -124,21 +158,20 @@ export class ObjectsTable extends Component {
|
|||
}
|
||||
|
||||
fetchCounts = async () => {
|
||||
const { allowedTypes } = this.props;
|
||||
const { queryText, visibleTypes } = parseQuery(this.state.activeQuery);
|
||||
|
||||
const filteredTypes = this.savedObjectTypes.filter(
|
||||
type => !visibleTypes || visibleTypes.includes(type)
|
||||
);
|
||||
const filteredTypes = allowedTypes.filter(type => !visibleTypes || visibleTypes.includes(type));
|
||||
|
||||
// These are the saved objects visible in the table.
|
||||
const filteredSavedObjectCounts = await getSavedObjectCounts(
|
||||
this.props.$http,
|
||||
this.props.http,
|
||||
filteredTypes,
|
||||
queryText
|
||||
);
|
||||
|
||||
const exportAllOptions = [];
|
||||
const exportAllSelectedOptions = {};
|
||||
const exportAllOptions: ExportAllOption[] = [];
|
||||
const exportAllSelectedOptions: Record<string, boolean> = {};
|
||||
|
||||
Object.keys(filteredSavedObjectCounts).forEach(id => {
|
||||
// Add this type as a bulk-export option.
|
||||
|
@ -147,17 +180,13 @@ export class ObjectsTable extends Component {
|
|||
label: `${id} (${filteredSavedObjectCounts[id] || 0})`,
|
||||
});
|
||||
|
||||
// Select it by defayult.
|
||||
// Select it by default.
|
||||
exportAllSelectedOptions[id] = true;
|
||||
});
|
||||
|
||||
// Fetch all the saved objects that exist so we can accurately populate the counts within
|
||||
// the table filter dropdown.
|
||||
const savedObjectCounts = await getSavedObjectCounts(
|
||||
this.props.$http,
|
||||
this.savedObjectTypes,
|
||||
queryText
|
||||
);
|
||||
const savedObjectCounts = await getSavedObjectCounts(this.props.http, allowedTypes, queryText);
|
||||
|
||||
this.setState(state => ({
|
||||
...state,
|
||||
|
@ -178,66 +207,64 @@ export class ObjectsTable extends Component {
|
|||
|
||||
debouncedFetch = debounce(async () => {
|
||||
const { activeQuery: query, page, perPage } = this.state;
|
||||
const { notifications, http, allowedTypes } = this.props;
|
||||
const { queryText, visibleTypes } = parseQuery(query);
|
||||
// "searchFields" is missing from the "findOptions" but gets injected via the API.
|
||||
// The API extracts the fields from each uiExports.savedObjectsManagement "defaultSearchField" attribute
|
||||
const findOptions = {
|
||||
const findOptions: SavedObjectsFindOptions = {
|
||||
search: queryText ? `${queryText}*` : undefined,
|
||||
perPage,
|
||||
page: page + 1,
|
||||
fields: ['id'],
|
||||
type: this.savedObjectTypes.filter(type => !visibleTypes || visibleTypes.includes(type)),
|
||||
type: allowedTypes.filter(type => !visibleTypes || visibleTypes.includes(type)),
|
||||
};
|
||||
if (findOptions.type.length > 1) {
|
||||
findOptions.sortField = 'type';
|
||||
}
|
||||
|
||||
let resp;
|
||||
try {
|
||||
resp = await findObjects(findOptions);
|
||||
const resp = await findObjects(http, findOptions);
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(({ activeQuery }) => {
|
||||
// ignore results for old requests
|
||||
if (activeQuery.text !== query.text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
savedObjects: resp.savedObjects,
|
||||
filteredItemCount: resp.total,
|
||||
isSearching: false,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
isSearching: false,
|
||||
});
|
||||
}
|
||||
toastNotifications.addDanger({
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.unableFindSavedObjectsNotificationMessage',
|
||||
'savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage',
|
||||
{ defaultMessage: 'Unable find saved objects' }
|
||||
),
|
||||
text: `${error}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(({ activeQuery }) => {
|
||||
// ignore results for old requests
|
||||
if (activeQuery.text !== query.text) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
savedObjects: resp.savedObjects,
|
||||
filteredItemCount: resp.total,
|
||||
isSearching: false,
|
||||
};
|
||||
});
|
||||
}, 300);
|
||||
|
||||
refreshData = async () => {
|
||||
await Promise.all([this.fetchSavedObjects(), this.fetchCounts()]);
|
||||
};
|
||||
|
||||
onSelectionChanged = selection => {
|
||||
onSelectionChanged = (selection: SavedObjectWithMetadata[]) => {
|
||||
this.setState({ selectedSavedObjects: selection });
|
||||
};
|
||||
|
||||
onQueryChange = ({ query }) => {
|
||||
onQueryChange = ({ query }: { query: Query }) => {
|
||||
// TODO: Use isSameQuery to compare new query with state.activeQuery to avoid re-fetching the
|
||||
// same data we already have.
|
||||
this.setState(
|
||||
|
@ -253,7 +280,7 @@ export class ObjectsTable extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
onTableChange = async table => {
|
||||
onTableChange = async (table: any) => {
|
||||
const { index: page, size: perPage } = table.page || {};
|
||||
|
||||
this.setState(
|
||||
|
@ -266,7 +293,7 @@ export class ObjectsTable extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
onShowRelationships = object => {
|
||||
onShowRelationships = (object: SavedObjectWithMetadata) => {
|
||||
this.setState({
|
||||
isShowingRelationships: true,
|
||||
relationshipObject: object,
|
||||
|
@ -280,16 +307,17 @@ export class ObjectsTable extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
onExport = async includeReferencesDeep => {
|
||||
onExport = async (includeReferencesDeep: boolean) => {
|
||||
const { selectedSavedObjects } = this.state;
|
||||
const { notifications, http } = this.props;
|
||||
const objectsToExport = selectedSavedObjects.map(obj => ({ id: obj.id, type: obj.type }));
|
||||
|
||||
let blob;
|
||||
try {
|
||||
blob = await fetchExportObjects(objectsToExport, includeReferencesDeep);
|
||||
blob = await fetchExportObjects(http, objectsToExport, includeReferencesDeep);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('kbn.management.objects.objectsTable.export.dangerNotification', {
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.translate('savedObjectsManagement.objectsTable.export.dangerNotification', {
|
||||
defaultMessage: 'Unable to generate export',
|
||||
}),
|
||||
});
|
||||
|
@ -304,24 +332,26 @@ export class ObjectsTable extends Component {
|
|||
|
||||
onExportAll = async () => {
|
||||
const { exportAllSelectedOptions, isIncludeReferencesDeepChecked, activeQuery } = this.state;
|
||||
const { notifications, http } = this.props;
|
||||
const { queryText } = parseQuery(activeQuery);
|
||||
const exportTypes = Object.entries(exportAllSelectedOptions).reduce((accum, [id, selected]) => {
|
||||
if (selected) {
|
||||
accum.push(id);
|
||||
}
|
||||
return accum;
|
||||
}, []);
|
||||
}, [] as string[]);
|
||||
|
||||
let blob;
|
||||
try {
|
||||
blob = await fetchExportByTypeAndSearch(
|
||||
http,
|
||||
exportTypes,
|
||||
queryText ? `${queryText}*` : undefined,
|
||||
isIncludeReferencesDeepChecked
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('kbn.management.objects.objectsTable.export.dangerNotification', {
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.translate('savedObjectsManagement.objectsTable.export.dangerNotification', {
|
||||
defaultMessage: 'Unable to generate export',
|
||||
}),
|
||||
});
|
||||
|
@ -335,11 +365,12 @@ export class ObjectsTable extends Component {
|
|||
this.setState({ isShowingExportAllOptionsModal: false });
|
||||
};
|
||||
|
||||
showExportSuccessMessage = exportDetails => {
|
||||
showExportSuccessMessage = (exportDetails: SavedObjectsExportResultDetails | undefined) => {
|
||||
const { notifications } = this.props;
|
||||
if (exportDetails && exportDetails.missingReferences.length > 0) {
|
||||
toastNotifications.addWarning({
|
||||
notifications.toasts.addWarning({
|
||||
title: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.export.successWithMissingRefsNotification',
|
||||
'savedObjectsManagement.objectsTable.export.successWithMissingRefsNotification',
|
||||
{
|
||||
defaultMessage:
|
||||
'Your file is downloading in the background. ' +
|
||||
|
@ -349,8 +380,8 @@ export class ObjectsTable extends Component {
|
|||
),
|
||||
});
|
||||
} else {
|
||||
toastNotifications.addSuccess({
|
||||
title: i18n.translate('kbn.management.objects.objectsTable.export.successNotification', {
|
||||
notifications.toasts.addSuccess({
|
||||
title: i18n.translate('savedObjectsManagement.objectsTable.export.successNotification', {
|
||||
defaultMessage: 'Your file is downloading in the background',
|
||||
}),
|
||||
});
|
||||
|
@ -412,30 +443,30 @@ export class ObjectsTable extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
getRelationships = async (type, id) => {
|
||||
return await getRelationships(
|
||||
type,
|
||||
id,
|
||||
this.savedObjectTypes,
|
||||
this.props.$http,
|
||||
this.props.basePath
|
||||
);
|
||||
getRelationships = async (type: string, id: string) => {
|
||||
const { allowedTypes, http } = this.props;
|
||||
return await getRelationships(http, type, id, allowedTypes);
|
||||
};
|
||||
|
||||
renderFlyout() {
|
||||
if (!this.state.isShowingImportFlyout) {
|
||||
return null;
|
||||
}
|
||||
const { applications } = this.props;
|
||||
const newIndexPatternUrl = applications.getUrlForApp('kibana', {
|
||||
path: '#/management/kibana/index_pattern',
|
||||
});
|
||||
|
||||
return (
|
||||
<Flyout
|
||||
close={this.hideImportFlyout}
|
||||
done={this.finishImport}
|
||||
services={this.props.services}
|
||||
http={this.props.http}
|
||||
serviceRegistry={this.props.serviceRegistry}
|
||||
indexPatterns={this.props.indexPatterns}
|
||||
newIndexPatternUrl={this.props.newIndexPatternUrl}
|
||||
savedObjectTypes={this.props.savedObjectTypes}
|
||||
confirmModalPromise={this.props.confirmModalPromise}
|
||||
newIndexPatternUrl={newIndexPatternUrl}
|
||||
allowedTypes={this.props.allowedTypes}
|
||||
overlays={this.props.overlays}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -447,10 +478,10 @@ export class ObjectsTable extends Component {
|
|||
|
||||
return (
|
||||
<Relationships
|
||||
savedObject={this.state.relationshipObject}
|
||||
basePath={this.props.http.basePath}
|
||||
savedObject={this.state.relationshipObject!}
|
||||
getRelationships={this.getRelationships}
|
||||
close={this.onHideRelationships}
|
||||
getDashboardUrl={this.props.getDashboardUrl}
|
||||
goInspectObject={this.props.goInspectObject}
|
||||
canGoInApp={this.props.canGoInApp}
|
||||
/>
|
||||
|
@ -482,7 +513,7 @@ export class ObjectsTable extends Component {
|
|||
<EuiConfirmModal
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModalTitle"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModalTitle"
|
||||
defaultMessage="Delete saved objects"
|
||||
/>
|
||||
}
|
||||
|
@ -491,19 +522,19 @@ export class ObjectsTable extends Component {
|
|||
buttonColor="danger"
|
||||
cancelButtonText={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
}
|
||||
confirmButtonText={
|
||||
isDeleting ? (
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteProcessButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.deleteProcessButtonLabel"
|
||||
defaultMessage="Deleting…"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel"
|
||||
defaultMessage="Delete"
|
||||
/>
|
||||
)
|
||||
|
@ -512,7 +543,7 @@ export class ObjectsTable extends Component {
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.deleteSavedObjectsConfirmModalDescription"
|
||||
id="savedObjectsManagement.deleteSavedObjectsConfirmModalDescription"
|
||||
defaultMessage="This action will delete the following saved objects:"
|
||||
/>
|
||||
</p>
|
||||
|
@ -522,7 +553,7 @@ export class ObjectsTable extends Component {
|
|||
{
|
||||
field: 'type',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName',
|
||||
'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName',
|
||||
{ defaultMessage: 'Type' }
|
||||
),
|
||||
width: '50px',
|
||||
|
@ -535,14 +566,14 @@ export class ObjectsTable extends Component {
|
|||
{
|
||||
field: 'id',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.idColumnName',
|
||||
'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName',
|
||||
{ defaultMessage: 'Id' }
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'meta.title',
|
||||
name: i18n.translate(
|
||||
'kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName',
|
||||
'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName',
|
||||
{ defaultMessage: 'Title' }
|
||||
),
|
||||
},
|
||||
|
@ -586,7 +617,7 @@ export class ObjectsTable extends Component {
|
|||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModalTitle"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModalTitle"
|
||||
defaultMessage="Export {filteredItemCount, plural, one{# object} other {# objects}}"
|
||||
values={{
|
||||
filteredItemCount,
|
||||
|
@ -598,7 +629,7 @@ export class ObjectsTable extends Component {
|
|||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModalDescription"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModalDescription"
|
||||
defaultMessage="Select which types to export"
|
||||
/>
|
||||
}
|
||||
|
@ -626,7 +657,7 @@ export class ObjectsTable extends Component {
|
|||
name="includeReferencesDeep"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel"
|
||||
defaultMessage="Include related objects"
|
||||
/>
|
||||
}
|
||||
|
@ -641,7 +672,7 @@ export class ObjectsTable extends Component {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={this.closeExportAllModal}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.cancelButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -649,7 +680,7 @@ export class ObjectsTable extends Component {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={this.onExportAll}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel"
|
||||
id="savedObjectsManagement.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel"
|
||||
defaultMessage="Export all"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -673,12 +704,13 @@ export class ObjectsTable extends Component {
|
|||
isSearching,
|
||||
savedObjectCounts,
|
||||
} = this.state;
|
||||
const { http, allowedTypes, applications } = this.props;
|
||||
|
||||
const selectionConfig = {
|
||||
onSelectionChange: this.onSelectionChanged,
|
||||
};
|
||||
|
||||
const filterOptions = this.savedObjectTypes.map(type => ({
|
||||
const filterOptions = allowedTypes.map(type => ({
|
||||
value: type,
|
||||
name: type,
|
||||
view: `${type} (${savedObjectCounts[type] || 0})`,
|
||||
|
@ -698,14 +730,16 @@ export class ObjectsTable extends Component {
|
|||
/>
|
||||
<EuiSpacer size="xs" />
|
||||
<Table
|
||||
basePath={http.basePath}
|
||||
itemId={'id'}
|
||||
actionRegistry={this.props.actionRegistry}
|
||||
selectionConfig={selectionConfig}
|
||||
selectedSavedObjects={selectedSavedObjects}
|
||||
onQueryChange={this.onQueryChange}
|
||||
onTableChange={this.onTableChange}
|
||||
filterOptions={filterOptions}
|
||||
onExport={this.onExport}
|
||||
canDelete={this.props.uiCapabilities.savedObjectsManagement.delete}
|
||||
canDelete={applications.capabilities.savedObjectsManagement.delete as boolean}
|
||||
onDelete={this.onDelete}
|
||||
goInspectObject={this.props.goInspectObject}
|
||||
pageIndex={page}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { SavedObjectReference } from '../../../../core/types';
|
||||
|
||||
export interface ObjectField {
|
||||
type: FieldType;
|
||||
name: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export type FieldType = 'text' | 'number' | 'boolean' | 'array' | 'json';
|
||||
|
||||
export interface FieldState {
|
||||
value?: any;
|
||||
invalid?: boolean;
|
||||
}
|
||||
|
||||
export interface SubmittedFormData {
|
||||
attributes: any;
|
||||
references: SavedObjectReference[];
|
||||
}
|
|
@ -17,23 +17,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { actionRegistryMock } from './services/action_registry.mock';
|
||||
import { actionServiceMock } from './services/action_service.mock';
|
||||
import { serviceRegistryMock } from './services/service_registry.mock';
|
||||
import { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart } from './plugin';
|
||||
|
||||
const createSetupContractMock = (): jest.Mocked<SavedObjectsManagementPluginSetup> => {
|
||||
const mock = {
|
||||
actionRegistry: actionRegistryMock.create(),
|
||||
actions: actionServiceMock.createSetup(),
|
||||
serviceRegistry: serviceRegistryMock.create(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
const createStartContractMock = (): jest.Mocked<SavedObjectsManagementPluginStart> => {
|
||||
const mock = {};
|
||||
const mock = {
|
||||
actions: actionServiceMock.createStart(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
export const savedObjectsManagementPluginMock = {
|
||||
createActionRegistry: actionRegistryMock.create,
|
||||
createServiceRegistry: serviceRegistryMock.create,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
import { coreMock } from '../../../core/public/mocks';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { homePluginMock } from '../../home/public/mocks';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { managementPluginMock } from '../../management/public/mocks';
|
||||
import { dataPluginMock } from '../../data/public/mocks';
|
||||
import { SavedObjectsManagementPlugin } from './plugin';
|
||||
|
||||
describe('SavedObjectsManagementPlugin', () => {
|
||||
|
@ -31,10 +34,13 @@ describe('SavedObjectsManagementPlugin', () => {
|
|||
|
||||
describe('#setup', () => {
|
||||
it('registers the saved_objects feature to the home plugin', async () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreSetup = coreMock.createSetup({
|
||||
pluginStartDeps: { data: dataPluginMock.createStartContract() },
|
||||
});
|
||||
const homeSetup = homePluginMock.createSetupContract();
|
||||
const managementSetup = managementPluginMock.createSetupContract();
|
||||
|
||||
await plugin.setup(coreSetup, { home: homeSetup });
|
||||
await plugin.setup(coreSetup, { home: homeSetup, management: managementSetup });
|
||||
|
||||
expect(homeSetup.featureCatalogue.register).toHaveBeenCalledTimes(1);
|
||||
expect(homeSetup.featureCatalogue.register).toHaveBeenCalledWith(
|
||||
|
|
|
@ -19,37 +19,59 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
|
||||
import { ManagementSetup } from '../../management/public';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import { DashboardStart } from '../../dashboard/public';
|
||||
import { DiscoverStart } from '../../discover/public';
|
||||
import { HomePublicPluginSetup, FeatureCatalogueCategory } from '../../home/public';
|
||||
import { VisualizationsStart } from '../../visualizations/public';
|
||||
import {
|
||||
SavedObjectsManagementActionRegistry,
|
||||
ISavedObjectsManagementActionRegistry,
|
||||
SavedObjectsManagementActionService,
|
||||
SavedObjectsManagementActionServiceSetup,
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
SavedObjectsManagementServiceRegistry,
|
||||
ISavedObjectsManagementServiceRegistry,
|
||||
} from './services';
|
||||
import { registerServices } from './register_services';
|
||||
|
||||
export interface SavedObjectsManagementPluginSetup {
|
||||
actionRegistry: ISavedObjectsManagementActionRegistry;
|
||||
actions: SavedObjectsManagementActionServiceSetup;
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface SavedObjectsManagementPluginStart {}
|
||||
export interface SavedObjectsManagementPluginStart {
|
||||
actions: SavedObjectsManagementActionServiceStart;
|
||||
}
|
||||
|
||||
export interface SetupDependencies {
|
||||
management: ManagementSetup;
|
||||
home: HomePublicPluginSetup;
|
||||
}
|
||||
|
||||
export interface StartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
dashboard?: DashboardStart;
|
||||
visualizations?: VisualizationsStart;
|
||||
discover?: DiscoverStart;
|
||||
}
|
||||
|
||||
export class SavedObjectsManagementPlugin
|
||||
implements
|
||||
Plugin<
|
||||
SavedObjectsManagementPluginSetup,
|
||||
SavedObjectsManagementPluginStart,
|
||||
SetupDependencies,
|
||||
{}
|
||||
StartDependencies
|
||||
> {
|
||||
private actionRegistry = new SavedObjectsManagementActionRegistry();
|
||||
private actionService = new SavedObjectsManagementActionService();
|
||||
private serviceRegistry = new SavedObjectsManagementServiceRegistry();
|
||||
|
||||
public setup(
|
||||
core: CoreSetup<{}>,
|
||||
{ home }: SetupDependencies
|
||||
core: CoreSetup<StartDependencies, SavedObjectsManagementPluginStart>,
|
||||
{ home, management }: SetupDependencies
|
||||
): SavedObjectsManagementPluginSetup {
|
||||
const actionSetup = this.actionService.setup();
|
||||
|
||||
home.featureCatalogue.register({
|
||||
id: 'saved_objects',
|
||||
title: i18n.translate('savedObjectsManagement.objects.savedObjectsTitle', {
|
||||
|
@ -65,12 +87,39 @@ export class SavedObjectsManagementPlugin
|
|||
category: FeatureCatalogueCategory.ADMIN,
|
||||
});
|
||||
|
||||
const kibanaSection = management.sections.getSection('kibana');
|
||||
if (!kibanaSection) {
|
||||
throw new Error('`kibana` management section not found.');
|
||||
}
|
||||
kibanaSection.registerApp({
|
||||
id: 'objects',
|
||||
title: i18n.translate('savedObjectsManagement.managementSectionLabel', {
|
||||
defaultMessage: 'Saved Objects',
|
||||
}),
|
||||
order: 10,
|
||||
mount: async mountParams => {
|
||||
const { mountManagementSection } = await import('./management_section');
|
||||
return mountManagementSection({
|
||||
core,
|
||||
serviceRegistry: this.serviceRegistry,
|
||||
mountParams,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// depends on `getStartServices`, should not be awaited
|
||||
registerServices(this.serviceRegistry, core.getStartServices);
|
||||
|
||||
return {
|
||||
actionRegistry: this.actionRegistry,
|
||||
actions: actionSetup,
|
||||
serviceRegistry: this.serviceRegistry,
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
return {};
|
||||
const actionStart = this.actionService.start();
|
||||
return {
|
||||
actions: actionStart,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { StartServicesAccessor } from '../../../core/public';
|
||||
import { SavedObjectsManagementPluginStart, StartDependencies } from './plugin';
|
||||
import { ISavedObjectsManagementServiceRegistry } from './services';
|
||||
|
||||
export const registerServices = async (
|
||||
registry: ISavedObjectsManagementServiceRegistry,
|
||||
getStartServices: StartServicesAccessor<StartDependencies, SavedObjectsManagementPluginStart>
|
||||
) => {
|
||||
const [coreStart, { dashboard, data, visualizations, discover }] = await getStartServices();
|
||||
|
||||
if (dashboard) {
|
||||
registry.register({
|
||||
id: 'savedDashboards',
|
||||
title: 'dashboards',
|
||||
service: dashboard.getSavedDashboardLoader(),
|
||||
});
|
||||
}
|
||||
|
||||
if (visualizations) {
|
||||
registry.register({
|
||||
id: 'savedVisualizations',
|
||||
title: 'visualizations',
|
||||
service: visualizations.savedVisualizationsLoader,
|
||||
});
|
||||
}
|
||||
|
||||
if (discover) {
|
||||
registry.register({
|
||||
id: 'savedSearches',
|
||||
title: 'searches',
|
||||
service: discover.savedSearches.createLoader({
|
||||
savedObjectsClient: coreStart.savedObjects.client,
|
||||
indexPatterns: data.indexPatterns,
|
||||
search: data.search,
|
||||
chrome: coreStart.chrome,
|
||||
overlays: coreStart.overlays,
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 {
|
||||
SavedObjectsManagementActionService,
|
||||
SavedObjectsManagementActionServiceSetup,
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
} from './action_service';
|
||||
|
||||
const createSetupMock = (): jest.Mocked<SavedObjectsManagementActionServiceSetup> => {
|
||||
const mock = {
|
||||
register: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
const createStartMock = (): jest.Mocked<SavedObjectsManagementActionServiceStart> => {
|
||||
const mock = {
|
||||
has: jest.fn(),
|
||||
getAll: jest.fn(),
|
||||
};
|
||||
|
||||
mock.has.mockReturnValue(true);
|
||||
mock.getAll.mockReturnValue([]);
|
||||
|
||||
return mock;
|
||||
};
|
||||
|
||||
const createServiceMock = (): jest.Mocked<PublicMethodsOf<SavedObjectsManagementActionService>> => {
|
||||
const mock = {
|
||||
setup: jest.fn().mockReturnValue(createSetupMock()),
|
||||
start: jest.fn().mockReturnValue(createStartMock()),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
export const actionServiceMock = {
|
||||
create: createServiceMock,
|
||||
createSetup: createSetupMock,
|
||||
createStart: createStartMock,
|
||||
};
|
|
@ -17,8 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsManagementActionRegistry } from './action_registry';
|
||||
import { SavedObjectsManagementAction } from './action_types';
|
||||
import {
|
||||
SavedObjectsManagementActionService,
|
||||
SavedObjectsManagementActionServiceSetup,
|
||||
} from './action_service';
|
||||
import { SavedObjectsManagementAction } from './types';
|
||||
|
||||
class DummyAction extends SavedObjectsManagementAction {
|
||||
constructor(public id: string) {
|
||||
|
@ -36,27 +39,30 @@ class DummyAction extends SavedObjectsManagementAction {
|
|||
}
|
||||
|
||||
describe('SavedObjectsManagementActionRegistry', () => {
|
||||
let registry: SavedObjectsManagementActionRegistry;
|
||||
let service: SavedObjectsManagementActionService;
|
||||
let setup: SavedObjectsManagementActionServiceSetup;
|
||||
|
||||
const createAction = (id: string): SavedObjectsManagementAction => {
|
||||
return new DummyAction(id);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new SavedObjectsManagementActionRegistry();
|
||||
service = new SavedObjectsManagementActionService();
|
||||
setup = service.setup();
|
||||
});
|
||||
|
||||
describe('#register', () => {
|
||||
it('allows actions to be registered and retrieved', () => {
|
||||
const action = createAction('foo');
|
||||
registry.register(action);
|
||||
expect(registry.getAll()).toContain(action);
|
||||
setup.register(action);
|
||||
const start = service.start();
|
||||
expect(start.getAll()).toContain(action);
|
||||
});
|
||||
|
||||
it('does not allow actions with duplicate ids to be registered', () => {
|
||||
const action = createAction('my-action');
|
||||
registry.register(action);
|
||||
expect(() => registry.register(action)).toThrowErrorMatchingInlineSnapshot(
|
||||
setup.register(action);
|
||||
expect(() => setup.register(action)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Saved Objects Management Action with id 'my-action' already exists"`
|
||||
);
|
||||
});
|
||||
|
@ -65,12 +71,14 @@ describe('SavedObjectsManagementActionRegistry', () => {
|
|||
describe('#has', () => {
|
||||
it('returns true when an action with a matching ID exists', () => {
|
||||
const action = createAction('existing-action');
|
||||
registry.register(action);
|
||||
expect(registry.has('existing-action')).toEqual(true);
|
||||
setup.register(action);
|
||||
const start = service.start();
|
||||
expect(start.has('existing-action')).toEqual(true);
|
||||
});
|
||||
|
||||
it(`returns false when an action doesn't exist`, () => {
|
||||
expect(registry.has('missing-action')).toEqual(false);
|
||||
const start = service.start();
|
||||
expect(start.has('missing-action')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,36 +17,44 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsManagementAction } from './action_types';
|
||||
|
||||
export type ISavedObjectsManagementActionRegistry = PublicMethodsOf<
|
||||
SavedObjectsManagementActionRegistry
|
||||
>;
|
||||
|
||||
export class SavedObjectsManagementActionRegistry {
|
||||
private readonly actions = new Map<string, SavedObjectsManagementAction>();
|
||||
import { SavedObjectsManagementAction } from './types';
|
||||
|
||||
export interface SavedObjectsManagementActionServiceSetup {
|
||||
/**
|
||||
* register given action in the registry.
|
||||
*/
|
||||
register(action: SavedObjectsManagementAction) {
|
||||
if (this.actions.has(action.id)) {
|
||||
throw new Error(`Saved Objects Management Action with id '${action.id}' already exists`);
|
||||
}
|
||||
this.actions.set(action.id, action);
|
||||
}
|
||||
register: (action: SavedObjectsManagementAction) => void;
|
||||
}
|
||||
|
||||
export interface SavedObjectsManagementActionServiceStart {
|
||||
/**
|
||||
* return true if the registry contains given action, false otherwise.
|
||||
*/
|
||||
has(actionId: string) {
|
||||
return this.actions.has(actionId);
|
||||
}
|
||||
|
||||
has: (actionId: string) => boolean;
|
||||
/**
|
||||
* return all {@link SavedObjectsManagementAction | actions} currently registered.
|
||||
*/
|
||||
getAll() {
|
||||
return [...this.actions.values()];
|
||||
getAll: () => SavedObjectsManagementAction[];
|
||||
}
|
||||
|
||||
export class SavedObjectsManagementActionService {
|
||||
private readonly actions = new Map<string, SavedObjectsManagementAction>();
|
||||
|
||||
setup(): SavedObjectsManagementActionServiceSetup {
|
||||
return {
|
||||
register: action => {
|
||||
if (this.actions.has(action.id)) {
|
||||
throw new Error(`Saved Objects Management Action with id '${action.id}' already exists`);
|
||||
}
|
||||
this.actions.set(action.id, action);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
start(): SavedObjectsManagementActionServiceStart {
|
||||
return {
|
||||
has: actionId => this.actions.has(actionId),
|
||||
getAll: () => [...this.actions.values()],
|
||||
};
|
||||
}
|
||||
}
|
|
@ -18,7 +18,13 @@
|
|||
*/
|
||||
|
||||
export {
|
||||
SavedObjectsManagementActionRegistry,
|
||||
ISavedObjectsManagementActionRegistry,
|
||||
} from './action_registry';
|
||||
export { SavedObjectsManagementAction, SavedObjectsManagementRecord } from './action_types';
|
||||
SavedObjectsManagementActionService,
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
SavedObjectsManagementActionServiceSetup,
|
||||
} from './action_service';
|
||||
export {
|
||||
SavedObjectsManagementServiceRegistry,
|
||||
ISavedObjectsManagementServiceRegistry,
|
||||
SavedObjectsManagementServiceRegistryEntry,
|
||||
} from './service_registry';
|
||||
export { SavedObjectsManagementAction, SavedObjectsManagementRecord } from './types';
|
||||
|
|
|
@ -17,21 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ISavedObjectsManagementActionRegistry } from './action_registry';
|
||||
import { ISavedObjectsManagementServiceRegistry } from './service_registry';
|
||||
|
||||
const createRegistryMock = (): jest.Mocked<ISavedObjectsManagementActionRegistry> => {
|
||||
const createRegistryMock = (): jest.Mocked<ISavedObjectsManagementServiceRegistry> => {
|
||||
const mock = {
|
||||
register: jest.fn(),
|
||||
has: jest.fn(),
|
||||
getAll: jest.fn(),
|
||||
all: jest.fn(),
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
mock.has.mockReturnValue(true);
|
||||
mock.getAll.mockReturnValue([]);
|
||||
mock.all.mockReturnValue([]);
|
||||
|
||||
return mock;
|
||||
};
|
||||
|
||||
export const actionRegistryMock = {
|
||||
export const serviceRegistryMock = {
|
||||
create: createRegistryMock,
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { SavedObjectLoader } from '../../../saved_objects/public';
|
||||
|
||||
export interface SavedObjectsManagementServiceRegistryEntry {
|
||||
id: string;
|
||||
service: SavedObjectLoader;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type ISavedObjectsManagementServiceRegistry = PublicMethodsOf<
|
||||
SavedObjectsManagementServiceRegistry
|
||||
>;
|
||||
|
||||
export class SavedObjectsManagementServiceRegistry {
|
||||
private readonly registry = new Map<string, SavedObjectsManagementServiceRegistryEntry>();
|
||||
|
||||
public register(entry: SavedObjectsManagementServiceRegistryEntry) {
|
||||
if (this.registry.has(entry.id)) {
|
||||
throw new Error('');
|
||||
}
|
||||
this.registry.set(entry.id, entry);
|
||||
}
|
||||
|
||||
public all(): SavedObjectsManagementServiceRegistryEntry[] {
|
||||
return [...this.registry.values()];
|
||||
}
|
||||
|
||||
public get(id: string): SavedObjectsManagementServiceRegistryEntry | undefined {
|
||||
return this.registry.get(id);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue