mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[CM] graph onboarding (#157698)
This commit is contained in:
parent
d7570424b2
commit
6abee5af88
33 changed files with 1222 additions and 108 deletions
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ContentManagementServicesDefinition as ServicesDefinition,
|
||||
Version,
|
||||
} from '@kbn/object-versioning';
|
||||
|
||||
// We export the versioned service definition from this file and not the barrel to avoid adding
|
||||
// the schemas in the "public" js bundle
|
||||
|
||||
import { serviceDefinition as v1 } from './v1/cm_services';
|
||||
|
||||
export const cmServicesDefinition: { [version: Version]: ServicesDefinition } = {
|
||||
1: v1,
|
||||
};
|
10
x-pack/plugins/graph/common/content_management/constants.ts
Normal file
10
x-pack/plugins/graph/common/content_management/constants.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const LATEST_VERSION = 1;
|
||||
|
||||
export const CONTENT_ID = 'graph';
|
31
x-pack/plugins/graph/common/content_management/index.ts
Normal file
31
x-pack/plugins/graph/common/content_management/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { LATEST_VERSION, CONTENT_ID } from './constants';
|
||||
|
||||
export type { GraphContentType } from './types';
|
||||
|
||||
export type {
|
||||
GraphSavedObject,
|
||||
PartialGraphSavedObject,
|
||||
GraphSavedObjectAttributes,
|
||||
GraphGetIn,
|
||||
GraphGetOut,
|
||||
GraphCreateIn,
|
||||
GraphCreateOut,
|
||||
CreateOptions,
|
||||
GraphUpdateIn,
|
||||
GraphUpdateOut,
|
||||
UpdateOptions,
|
||||
GraphDeleteIn,
|
||||
GraphDeleteOut,
|
||||
GraphSearchIn,
|
||||
GraphSearchOut,
|
||||
GraphSearchQuery,
|
||||
} from './latest';
|
||||
|
||||
export * as GraphV1 from './v1';
|
8
x-pack/plugins/graph/common/content_management/latest.ts
Normal file
8
x-pack/plugins/graph/common/content_management/latest.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
8
x-pack/plugins/graph/common/content_management/types.ts
Normal file
8
x-pack/plugins/graph/common/content_management/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type GraphContentType = 'graph';
|
142
x-pack/plugins/graph/common/content_management/v1/cm_services.ts
Normal file
142
x-pack/plugins/graph/common/content_management/v1/cm_services.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning';
|
||||
|
||||
const apiError = schema.object({
|
||||
error: schema.string(),
|
||||
message: schema.string(),
|
||||
statusCode: schema.number(),
|
||||
metadata: schema.object({}, { unknowns: 'allow' }),
|
||||
});
|
||||
|
||||
const referenceSchema = schema.object(
|
||||
{
|
||||
name: schema.maybe(schema.string()),
|
||||
type: schema.string(),
|
||||
id: schema.string(),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const referencesSchema = schema.arrayOf(referenceSchema);
|
||||
|
||||
const graphAttributesSchema = schema.object(
|
||||
{
|
||||
title: schema.string(),
|
||||
description: schema.maybe(schema.string()),
|
||||
version: schema.maybe(schema.number()),
|
||||
numLinks: schema.number(),
|
||||
numVertices: schema.number(),
|
||||
kibanaSavedObjectMeta: schema.maybe(schema.any()),
|
||||
wsState: schema.maybe(schema.string()),
|
||||
legacyIndexPatternRef: schema.maybe(schema.string()),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const graphSavedObjectSchema = schema.object(
|
||||
{
|
||||
id: schema.string(),
|
||||
type: schema.string(),
|
||||
version: schema.maybe(schema.string()),
|
||||
createdAt: schema.maybe(schema.string()),
|
||||
updatedAt: schema.maybe(schema.string()),
|
||||
error: schema.maybe(apiError),
|
||||
attributes: graphAttributesSchema,
|
||||
references: referencesSchema,
|
||||
namespaces: schema.maybe(schema.arrayOf(schema.string())),
|
||||
originId: schema.maybe(schema.string()),
|
||||
},
|
||||
{ unknowns: 'allow' }
|
||||
);
|
||||
|
||||
const getResultSchema = schema.object(
|
||||
{
|
||||
item: graphSavedObjectSchema,
|
||||
meta: schema.object(
|
||||
{
|
||||
outcome: schema.oneOf([
|
||||
schema.literal('exactMatch'),
|
||||
schema.literal('aliasMatch'),
|
||||
schema.literal('conflict'),
|
||||
]),
|
||||
aliasTargetId: schema.maybe(schema.string()),
|
||||
aliasPurpose: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal('savedObjectConversion'),
|
||||
schema.literal('savedObjectImport'),
|
||||
])
|
||||
),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const createOptionsSchema = schema.object({
|
||||
overwrite: schema.maybe(schema.boolean()),
|
||||
references: schema.maybe(referencesSchema),
|
||||
});
|
||||
|
||||
// Content management service definition.
|
||||
// We need it for BWC support between different versions of the content
|
||||
export const serviceDefinition: ServicesDefinition = {
|
||||
get: {
|
||||
out: {
|
||||
result: {
|
||||
schema: getResultSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema,
|
||||
},
|
||||
data: {
|
||||
schema: graphAttributesSchema,
|
||||
},
|
||||
},
|
||||
out: {
|
||||
result: {
|
||||
schema: schema.object(
|
||||
{
|
||||
item: graphSavedObjectSchema,
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema, // same schema as "create"
|
||||
},
|
||||
data: {
|
||||
schema: graphAttributesSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
in: {
|
||||
options: {
|
||||
schema: schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
searchFields: schema.maybe(schema.arrayOf(schema.string())),
|
||||
types: schema.maybe(schema.arrayOf(schema.string())),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
)
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
26
x-pack/plugins/graph/common/content_management/v1/index.ts
Normal file
26
x-pack/plugins/graph/common/content_management/v1/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type {
|
||||
GraphSavedObject,
|
||||
PartialGraphSavedObject,
|
||||
GraphSavedObjectAttributes,
|
||||
GraphGetIn,
|
||||
GraphGetOut,
|
||||
GraphCreateIn,
|
||||
GraphCreateOut,
|
||||
CreateOptions,
|
||||
GraphUpdateIn,
|
||||
GraphUpdateOut,
|
||||
UpdateOptions,
|
||||
GraphDeleteIn,
|
||||
GraphDeleteOut,
|
||||
GraphSearchIn,
|
||||
GraphSearchOut,
|
||||
GraphSearchQuery,
|
||||
Reference,
|
||||
} from './types';
|
115
x-pack/plugins/graph/common/content_management/v1/types.ts
Normal file
115
x-pack/plugins/graph/common/content_management/v1/types.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
GetIn,
|
||||
CreateIn,
|
||||
SearchIn,
|
||||
UpdateIn,
|
||||
DeleteIn,
|
||||
DeleteResult,
|
||||
SearchResult,
|
||||
GetResult,
|
||||
CreateResult,
|
||||
UpdateResult,
|
||||
} from '@kbn/content-management-plugin/common';
|
||||
|
||||
import { GraphContentType } from '../types';
|
||||
|
||||
export interface Reference {
|
||||
type: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type GraphSavedObjectAttributes = {
|
||||
title: string;
|
||||
description?: string;
|
||||
version?: number;
|
||||
numVertices: number;
|
||||
numLinks: number;
|
||||
wsState?: string;
|
||||
kibanaSavedObjectMeta?: unknown;
|
||||
legacyIndexPatternRef: string;
|
||||
};
|
||||
|
||||
export interface GraphSavedObject {
|
||||
id: string;
|
||||
type: string;
|
||||
version?: string;
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
attributes: GraphSavedObjectAttributes;
|
||||
references: Reference[];
|
||||
namespaces?: string[];
|
||||
originId?: string;
|
||||
error?: {
|
||||
error: string;
|
||||
message: string;
|
||||
statusCode: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
export type PartialGraphSavedObject = Omit<GraphSavedObject, 'attributes' | 'references'> & {
|
||||
attributes: Partial<GraphSavedObjectAttributes>;
|
||||
references: Reference[] | undefined;
|
||||
};
|
||||
// ----------- GET --------------
|
||||
|
||||
export type GraphGetIn = GetIn<GraphContentType>;
|
||||
|
||||
export type GraphGetOut = GetResult<
|
||||
GraphSavedObject,
|
||||
{
|
||||
outcome: 'exactMatch' | 'aliasMatch' | 'conflict';
|
||||
aliasTargetId?: string;
|
||||
aliasPurpose?: 'savedObjectConversion' | 'savedObjectImport';
|
||||
}
|
||||
>;
|
||||
|
||||
// ----------- CREATE --------------
|
||||
|
||||
export interface CreateOptions {
|
||||
/** If a document with the given `id` already exists, overwrite it's contents (default=false). */
|
||||
overwrite?: boolean;
|
||||
/** Array of referenced saved objects. */
|
||||
references?: Reference[];
|
||||
}
|
||||
|
||||
export type GraphCreateIn = CreateIn<GraphContentType, GraphSavedObjectAttributes, CreateOptions>;
|
||||
|
||||
export type GraphCreateOut = CreateResult<GraphSavedObject>;
|
||||
|
||||
// ----------- UPDATE --------------
|
||||
|
||||
export interface UpdateOptions {
|
||||
/** Array of referenced saved objects. */
|
||||
references?: Reference[];
|
||||
}
|
||||
|
||||
export type GraphUpdateIn = UpdateIn<GraphContentType, GraphSavedObjectAttributes, UpdateOptions>;
|
||||
|
||||
export type GraphUpdateOut = UpdateResult<PartialGraphSavedObject>;
|
||||
|
||||
// ----------- DELETE --------------
|
||||
|
||||
export type GraphDeleteIn = DeleteIn<GraphContentType>;
|
||||
|
||||
export type GraphDeleteOut = DeleteResult;
|
||||
|
||||
// ----------- SEARCH --------------
|
||||
|
||||
export interface GraphSearchQuery {
|
||||
types?: string[];
|
||||
searchFields?: string[];
|
||||
}
|
||||
|
||||
export type GraphSearchIn = SearchIn<GraphContentType, {}>;
|
||||
|
||||
export type GraphSearchOut = SearchResult<GraphSavedObject>;
|
|
@ -19,6 +19,7 @@
|
|||
"inspector",
|
||||
"savedObjectsManagement",
|
||||
"savedObjectsFinder",
|
||||
"contentManagement"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"home",
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
ChromeStart,
|
||||
CoreStart,
|
||||
PluginInitializerContext,
|
||||
SavedObjectsClientContract,
|
||||
ToastsStart,
|
||||
OverlayStart,
|
||||
AppMountParameters,
|
||||
|
@ -32,10 +31,10 @@ import { TableListViewKibanaProvider } from '@kbn/content-management-table-list'
|
|||
|
||||
import './index.scss';
|
||||
import('./font_awesome');
|
||||
import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public';
|
||||
import { SpacesApi } from '@kbn/spaces-plugin/public';
|
||||
import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { GraphSavePolicy } from './types';
|
||||
import { graphRouter } from './router';
|
||||
import { checkLicense } from '../common/check_license';
|
||||
|
@ -60,14 +59,13 @@ export interface GraphDependencies {
|
|||
indexPatterns: DataViewsContract;
|
||||
data: ReturnType<DataPlugin['start']>;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentClient: ContentClient;
|
||||
addBasePath: (url: string) => string;
|
||||
getBasePath: () => string;
|
||||
storage: Storage;
|
||||
canEditDrillDownUrls: boolean;
|
||||
graphSavePolicy: GraphSavePolicy;
|
||||
overlays: OverlayStart;
|
||||
savedObjects: SavedObjectsStart;
|
||||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
uiSettings: IUiSettingsClient;
|
||||
history: ScopedHistory<unknown>;
|
||||
|
|
|
@ -41,7 +41,7 @@ export interface ListingRouteProps {
|
|||
}
|
||||
|
||||
export function ListingRoute({
|
||||
deps: { chrome, savedObjectsClient, coreStart, capabilities, addBasePath, uiSettings },
|
||||
deps: { chrome, contentClient, coreStart, capabilities, addBasePath, uiSettings },
|
||||
}: ListingRouteProps) {
|
||||
const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
|
||||
const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
|
||||
|
@ -60,7 +60,7 @@ export function ListingRoute({
|
|||
const findItems = useCallback(
|
||||
(search: string) => {
|
||||
return findSavedWorkspace(
|
||||
{ savedObjectsClient, basePath: coreStart.http.basePath },
|
||||
{ contentClient, basePath: coreStart.http.basePath },
|
||||
search,
|
||||
listingLimit
|
||||
).then(({ total, hits }) => ({
|
||||
|
@ -68,7 +68,7 @@ export function ListingRoute({
|
|||
hits: hits.map(toTableListViewSavedObject),
|
||||
}));
|
||||
},
|
||||
[coreStart.http.basePath, listingLimit, savedObjectsClient]
|
||||
[coreStart.http.basePath, listingLimit, contentClient]
|
||||
);
|
||||
|
||||
const editItem = useCallback(
|
||||
|
@ -81,11 +81,11 @@ export function ListingRoute({
|
|||
const deleteItems = useCallback(
|
||||
async (savedWorkspaces: Array<{ id: string }>) => {
|
||||
await deleteSavedWorkspace(
|
||||
savedObjectsClient,
|
||||
contentClient,
|
||||
savedWorkspaces.map((cur) => cur.id!)
|
||||
);
|
||||
},
|
||||
[savedObjectsClient]
|
||||
[contentClient]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -27,7 +27,7 @@ export const WorkspaceRoute = ({
|
|||
deps: {
|
||||
toastNotifications,
|
||||
coreStart,
|
||||
savedObjectsClient,
|
||||
contentClient,
|
||||
graphSavePolicy,
|
||||
chrome,
|
||||
canEditDrillDownUrls,
|
||||
|
@ -108,8 +108,8 @@ export const WorkspaceRoute = ({
|
|||
notifications: coreStart.notifications,
|
||||
http: coreStart.http,
|
||||
overlays: coreStart.overlays,
|
||||
savedObjectsClient,
|
||||
savePolicy: graphSavePolicy,
|
||||
contentClient,
|
||||
changeUrl: (newUrl) => history.push(newUrl),
|
||||
notifyReact: () => setRenderCounter((cur) => cur + 1),
|
||||
chrome,
|
||||
|
@ -120,7 +120,7 @@ export const WorkspaceRoute = ({
|
|||
const loaded = useWorkspaceLoader({
|
||||
workspaceRef,
|
||||
store,
|
||||
savedObjectsClient,
|
||||
contentClient,
|
||||
spaces,
|
||||
coreStart,
|
||||
data,
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { OverlayStart } from '@kbn/core/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import type { GraphWorkspaceSavedObject } from '../../types';
|
||||
import { SAVE_DUPLICATE_REJECTED } from './constants';
|
||||
import { findObjectByTitle } from './find_object_by_title';
|
||||
import { displayDuplicateTitleConfirmModal } from './display_duplicate_title_confirm_modal';
|
||||
|
||||
/**
|
||||
* check for an existing VisSavedObject with the same title in ES
|
||||
* returns Promise<true> when it's no duplicate, or the modal displaying the warning
|
||||
* that's there's a duplicate is confirmed, else it returns a rejected Promise<ErrorMsg>
|
||||
* @param savedObject
|
||||
* @param isTitleDuplicateConfirmed
|
||||
* @param onTitleDuplicate
|
||||
* @param services
|
||||
*/
|
||||
export async function checkForDuplicateTitle(
|
||||
savedObject: Pick<GraphWorkspaceSavedObject, 'id' | 'title' | 'lastSavedTitle' | 'getEsType'>,
|
||||
isTitleDuplicateConfirmed: boolean,
|
||||
onTitleDuplicate: (() => void) | undefined,
|
||||
services: {
|
||||
overlays: OverlayStart;
|
||||
contentClient: ContentClient;
|
||||
}
|
||||
): Promise<boolean> {
|
||||
const { overlays, contentClient } = services;
|
||||
// Don't check for duplicates if user has already confirmed save with duplicate title
|
||||
if (isTitleDuplicateConfirmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't check if the user isn't updating the title, otherwise that would become very annoying to have
|
||||
// to confirm the save every time, except when copyOnSave is true, then we do want to check.
|
||||
if (savedObject.title === savedObject.lastSavedTitle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const duplicate = await findObjectByTitle(contentClient, savedObject.title);
|
||||
|
||||
if (!duplicate || duplicate.id === savedObject.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (onTitleDuplicate) {
|
||||
onTitleDuplicate();
|
||||
return Promise.reject(new Error(SAVE_DUPLICATE_REJECTED));
|
||||
}
|
||||
|
||||
// TODO: make onTitleDuplicate a required prop and remove UI components from this class
|
||||
// Need to leave here until all users pass onTitleDuplicate.
|
||||
return displayDuplicateTitleConfirmModal(savedObject, overlays);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { OverlayStart } from '@kbn/core/public';
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
export function confirmModalPromise(
|
||||
message = '',
|
||||
title = '',
|
||||
confirmBtnText = '',
|
||||
overlays: OverlayStart
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cancelButtonText = i18n.translate('xpack.graph.confirmModal.cancelButtonLabel', {
|
||||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
const modal = overlays.openModal(
|
||||
toMountPoint(
|
||||
<EuiConfirmModal
|
||||
onCancel={() => {
|
||||
modal.close();
|
||||
reject();
|
||||
}}
|
||||
onConfirm={() => {
|
||||
modal.close();
|
||||
resolve(true);
|
||||
}}
|
||||
confirmButtonText={confirmBtnText}
|
||||
cancelButtonText={cancelButtonText}
|
||||
title={title}
|
||||
>
|
||||
{message}
|
||||
</EuiConfirmModal>
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
/** An error message to be used when the user rejects a confirm overwrite. */
|
||||
export const OVERWRITE_REJECTED = i18n.translate('xpack.graph.overwriteRejectedDescription', {
|
||||
defaultMessage: 'Overwrite confirmation was rejected',
|
||||
});
|
||||
|
||||
/** An error message to be used when the user rejects a confirm save with duplicate title. */
|
||||
export const SAVE_DUPLICATE_REJECTED = i18n.translate(
|
||||
'xpack.graph.saveDuplicateRejectedDescription',
|
||||
{
|
||||
defaultMessage: 'Save with duplicate title confirmation was rejected',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { OverlayStart } from '@kbn/core/public';
|
||||
import type { GraphWorkspaceSavedObject } from '../../types';
|
||||
import { SAVE_DUPLICATE_REJECTED } from './constants';
|
||||
import { confirmModalPromise } from './confirm_modal_promise';
|
||||
|
||||
export function displayDuplicateTitleConfirmModal(
|
||||
savedObject: Pick<GraphWorkspaceSavedObject, 'title'>,
|
||||
overlays: OverlayStart
|
||||
): Promise<boolean> {
|
||||
const confirmTitle = i18n.translate('xpack.graph.confirmModal.saveDuplicateConfirmationTitle', {
|
||||
defaultMessage: `This visualization already exists`,
|
||||
});
|
||||
|
||||
const confirmMessage = i18n.translate(
|
||||
'xpack.graph.confirmModal.saveDuplicateConfirmationMessage',
|
||||
{
|
||||
defaultMessage: `Saving "{name}" creates a duplicate title. Would you like to save anyway?`,
|
||||
values: { name: savedObject.title },
|
||||
}
|
||||
);
|
||||
|
||||
const confirmButtonText = i18n.translate('xpack.graph.confirmModal.saveDuplicateButtonLabel', {
|
||||
defaultMessage: 'Save',
|
||||
});
|
||||
|
||||
try {
|
||||
return confirmModalPromise(confirmMessage, confirmTitle, confirmButtonText, overlays);
|
||||
} catch {
|
||||
return Promise.reject(new Error(SAVE_DUPLICATE_REJECTED));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { findObjectByTitle } from './find_object_by_title';
|
||||
import { SimpleSavedObject } from '@kbn/core/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
|
||||
const mockFindContent = jest.fn(async () => ({
|
||||
pagination: { total: 0 },
|
||||
hits: [] as unknown[],
|
||||
}));
|
||||
const mockGetContent = jest.fn(async () => ({
|
||||
item: {
|
||||
id: 'test',
|
||||
references: [
|
||||
{
|
||||
id: 'test',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
visState: JSON.stringify({ type: 'area' }),
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: '{filter: []}',
|
||||
},
|
||||
},
|
||||
_version: '1',
|
||||
},
|
||||
meta: {
|
||||
outcome: 'exact',
|
||||
alias_target_id: null,
|
||||
},
|
||||
}));
|
||||
const mockCreateContent = jest.fn(async (input: any) => ({
|
||||
item: {
|
||||
id: 'test',
|
||||
},
|
||||
}));
|
||||
|
||||
const mockUpdateContent = jest.fn(() => ({
|
||||
item: {
|
||||
id: 'test',
|
||||
},
|
||||
}));
|
||||
|
||||
const contentClientMock = {
|
||||
create: mockCreateContent,
|
||||
update: mockUpdateContent,
|
||||
get: mockGetContent,
|
||||
search: mockFindContent,
|
||||
} as unknown as ContentClient;
|
||||
|
||||
describe('findObjectByTitle', () => {
|
||||
beforeEach(() => {
|
||||
mockFindContent.mockClear();
|
||||
});
|
||||
|
||||
it('returns undefined if title is not provided', async () => {
|
||||
const match = await findObjectByTitle(contentClientMock, '');
|
||||
expect(match).toBeUndefined();
|
||||
});
|
||||
|
||||
it('matches any case', async () => {
|
||||
const indexPattern = {
|
||||
attributes: { title: 'foo' },
|
||||
} as SimpleSavedObject;
|
||||
|
||||
mockFindContent.mockImplementation(async () => ({
|
||||
hits: [indexPattern],
|
||||
pagination: { total: 1 },
|
||||
}));
|
||||
const match = await findObjectByTitle(contentClientMock, 'FOO');
|
||||
expect(match).toEqual(indexPattern);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { CONTENT_ID, GraphSearchIn, GraphSearchOut } from '../../../common/content_management';
|
||||
|
||||
/** Returns an object matching a given title */
|
||||
export async function findObjectByTitle(contentClient: ContentClient, title: string) {
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Elastic search will return the most relevant results first, which means exact matches should come
|
||||
// first, and so we shouldn't need to request everything. Using 10 just to be on the safe side.
|
||||
const response = await contentClient.search<GraphSearchIn, GraphSearchOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
query: {
|
||||
text: `"${title}"`,
|
||||
},
|
||||
options: {
|
||||
searchFields: ['title'],
|
||||
},
|
||||
});
|
||||
return response.hits.find((obj) => obj.attributes.title.toLowerCase() === title.toLowerCase());
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { saveWithConfirmation } from './save_with_confirmation';
|
||||
export { checkForDuplicateTitle } from './check_for_duplicate_title';
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { SavedObjectsCreateOptions, OverlayStart } from '@kbn/core/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { CONTENT_ID, GraphCreateIn, GraphCreateOut } from '../../../common/content_management';
|
||||
import { OVERWRITE_REJECTED } from './constants';
|
||||
import { confirmModalPromise } from './confirm_modal_promise';
|
||||
import { GraphSavedObjectAttributes, GraphSavedObject } from '../../../common/content_management';
|
||||
import { GraphWorkspaceSavedObject } from '../../types';
|
||||
|
||||
/**
|
||||
* Attempts to create the current object using the serialized source. If an object already
|
||||
* exists, a warning message requests an overwrite confirmation.
|
||||
* @param source - serialized version of this object what will be indexed into elasticsearch.
|
||||
* @param savedObject - VisSavedObject
|
||||
* @param options - options to pass to the saved object create method
|
||||
* @param services - provides Kibana services savedObjectsClient and overlays
|
||||
* @returns {Promise} - A promise that is resolved with the objects id if the object is
|
||||
* successfully indexed. If the overwrite confirmation was rejected, an error is thrown with
|
||||
* a confirmRejected = true parameter so that case can be handled differently than
|
||||
* a create or index error.
|
||||
* @resolved {SimpleSavedObject}
|
||||
*/
|
||||
export async function saveWithConfirmation(
|
||||
source: GraphSavedObjectAttributes,
|
||||
savedObject: Pick<GraphWorkspaceSavedObject, 'title' | 'getEsType' | 'displayName'>,
|
||||
options: SavedObjectsCreateOptions,
|
||||
services: { overlays: OverlayStart; contentClient: ContentClient }
|
||||
): Promise<{ item: GraphSavedObject }> {
|
||||
const { overlays, contentClient } = services;
|
||||
try {
|
||||
return await contentClient.create<GraphCreateIn, GraphCreateOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
data: source,
|
||||
options,
|
||||
});
|
||||
} catch (err) {
|
||||
// record exists, confirm overwriting
|
||||
if (get(err, 'res.status') === 409) {
|
||||
const confirmMessage = i18n.translate(
|
||||
'xpack.graph.confirmModal.overwriteConfirmationMessage',
|
||||
{
|
||||
defaultMessage: 'Are you sure you want to overwrite {title}?',
|
||||
values: { title: savedObject.title },
|
||||
}
|
||||
);
|
||||
|
||||
const title = i18n.translate('xpack.graph.confirmModal.overwriteTitle', {
|
||||
defaultMessage: 'Overwrite {name}?',
|
||||
values: { name: savedObject.displayName },
|
||||
});
|
||||
const confirmButtonText = i18n.translate('xpack.graph.confirmModal.overwriteButtonLabel', {
|
||||
defaultMessage: 'Overwrite',
|
||||
});
|
||||
|
||||
return confirmModalPromise(confirmMessage, title, confirmButtonText, overlays)
|
||||
.then(() =>
|
||||
contentClient.create<GraphCreateIn, GraphCreateOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
data: source,
|
||||
options: {
|
||||
overwrite: true,
|
||||
...options,
|
||||
},
|
||||
})
|
||||
)
|
||||
.catch(() => Promise.reject(new Error(OVERWRITE_REJECTED)));
|
||||
}
|
||||
return await Promise.reject(err);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { GraphWorkspaceSavedObject } from '../types';
|
||||
import { saveSavedWorkspace } from './saved_workspace_utils';
|
||||
|
||||
|
@ -28,11 +29,9 @@ describe('saved_workspace_utils', () => {
|
|||
savedWorkspace,
|
||||
{},
|
||||
{
|
||||
savedObjectsClient: {
|
||||
...core.savedObjects.client,
|
||||
find: jest.fn().mockResolvedValue({ savedObjects: [] }),
|
||||
create: jest.fn().mockResolvedValue({ id: '456' }),
|
||||
},
|
||||
contentClient: {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ item: { id: '456' } })),
|
||||
} as unknown as ContentClient,
|
||||
overlays: core.overlays,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,26 +7,31 @@
|
|||
|
||||
import { cloneDeep, assign, defaults, forOwn } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
IBasePath,
|
||||
OverlayStart,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectAttributes,
|
||||
} from '@kbn/core/public';
|
||||
import { IBasePath, OverlayStart, SavedObjectAttributes } from '@kbn/core/public';
|
||||
|
||||
import {
|
||||
SavedObjectSaveOpts,
|
||||
checkForDuplicateTitle,
|
||||
saveWithConfirmation,
|
||||
isErrorNonFatal,
|
||||
} from '@kbn/saved-objects-plugin/public';
|
||||
import { SavedObjectSaveOpts, isErrorNonFatal } from '@kbn/saved-objects-plugin/public';
|
||||
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import {
|
||||
GraphGetIn,
|
||||
GraphGetOut,
|
||||
GraphSearchIn,
|
||||
GraphSearchOut,
|
||||
GraphDeleteIn,
|
||||
GraphDeleteOut,
|
||||
GraphCreateIn,
|
||||
GraphCreateOut,
|
||||
GraphSavedObjectAttributes,
|
||||
GraphUpdateOut,
|
||||
GraphUpdateIn,
|
||||
CONTENT_ID,
|
||||
} from '../../common/content_management';
|
||||
import {
|
||||
injectReferences,
|
||||
extractReferences,
|
||||
} from '../services/persistence/saved_workspace_references';
|
||||
import { GraphWorkspaceSavedObject } from '../types';
|
||||
|
||||
import { checkForDuplicateTitle, saveWithConfirmation } from './saved_objects_utils';
|
||||
const savedWorkspaceType = 'graph-workspace';
|
||||
const mapping: Record<string, string> = {
|
||||
title: 'text',
|
||||
|
@ -60,25 +65,25 @@ function mapHits(hit: any, url: string): GraphWorkspaceSavedObject {
|
|||
|
||||
interface SavedWorkspaceServices {
|
||||
basePath: IBasePath;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentClient: ContentClient;
|
||||
}
|
||||
|
||||
export function findSavedWorkspace(
|
||||
{ savedObjectsClient, basePath }: SavedWorkspaceServices,
|
||||
{ contentClient, basePath }: SavedWorkspaceServices,
|
||||
searchString: string,
|
||||
size: number = 100
|
||||
) {
|
||||
return savedObjectsClient
|
||||
.find<Record<string, unknown>>({
|
||||
type: savedWorkspaceType,
|
||||
search: searchString ? `${searchString}*` : undefined,
|
||||
perPage: size,
|
||||
searchFields: ['title^3', 'description'],
|
||||
return contentClient
|
||||
.search<GraphSearchIn, GraphSearchOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
query: {
|
||||
text: searchString ? `${searchString}*` : '',
|
||||
},
|
||||
})
|
||||
.then((resp) => {
|
||||
return {
|
||||
total: resp.total,
|
||||
hits: resp.savedObjects.map((hit) => mapHits(hit, urlFor(basePath, hit.id))),
|
||||
total: resp.pagination.total,
|
||||
hits: resp.hits.map((hit) => mapHits(hit, urlFor(basePath, hit.id))),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -93,18 +98,15 @@ export function getEmptyWorkspace() {
|
|||
};
|
||||
}
|
||||
|
||||
export async function getSavedWorkspace(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
id: string
|
||||
) {
|
||||
const resolveResult = await savedObjectsClient.resolve<Record<string, unknown>>(
|
||||
savedWorkspaceType,
|
||||
id
|
||||
);
|
||||
export async function getSavedWorkspace(contentClient: ContentClient, id: string) {
|
||||
const resolveResult = await contentClient.get<GraphGetIn, GraphGetOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
id,
|
||||
});
|
||||
|
||||
const resp = resolveResult.saved_object;
|
||||
const resp = resolveResult.item;
|
||||
|
||||
if (!resp._version) {
|
||||
if (!resp.attributes) {
|
||||
throw new SavedObjectNotFound(savedWorkspaceType, id || '');
|
||||
}
|
||||
|
||||
|
@ -112,8 +114,10 @@ export async function getSavedWorkspace(
|
|||
id,
|
||||
displayName: 'graph workspace',
|
||||
getEsType: () => savedWorkspaceType,
|
||||
_source: cloneDeep(resp.attributes),
|
||||
} as GraphWorkspaceSavedObject;
|
||||
_source: cloneDeep({
|
||||
...resp.attributes,
|
||||
}),
|
||||
} as unknown as GraphWorkspaceSavedObject;
|
||||
|
||||
// assign the defaults to the response
|
||||
defaults(savedObject._source, defaultsProps);
|
||||
|
@ -132,9 +136,9 @@ export async function getSavedWorkspace(
|
|||
}
|
||||
|
||||
const sharingSavedObjectProps = {
|
||||
outcome: resolveResult.outcome,
|
||||
aliasTargetId: resolveResult.alias_target_id,
|
||||
aliasPurpose: resolveResult.alias_purpose,
|
||||
outcome: resolveResult.meta.outcome,
|
||||
aliasTargetId: resolveResult.meta.aliasTargetId,
|
||||
aliasPurpose: resolveResult.meta.aliasPurpose,
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -143,11 +147,15 @@ export async function getSavedWorkspace(
|
|||
};
|
||||
}
|
||||
|
||||
export function deleteSavedWorkspace(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
ids: string[]
|
||||
) {
|
||||
return Promise.all(ids.map((id: string) => savedObjectsClient.delete(savedWorkspaceType, id)));
|
||||
export function deleteSavedWorkspace(contentClient: ContentClient, ids: string[]) {
|
||||
return Promise.all(
|
||||
ids.map((id: string) =>
|
||||
contentClient.delete<GraphDeleteIn, GraphDeleteOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
id,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function saveSavedWorkspace(
|
||||
|
@ -158,7 +166,7 @@ export async function saveSavedWorkspace(
|
|||
onTitleDuplicate,
|
||||
}: SavedObjectSaveOpts = {},
|
||||
services: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentClient: ContentClient;
|
||||
overlays: OverlayStart;
|
||||
}
|
||||
) {
|
||||
|
@ -208,13 +216,33 @@ export async function saveSavedWorkspace(
|
|||
references,
|
||||
};
|
||||
const resp = confirmOverwrite
|
||||
? await saveWithConfirmation(attributes, savedObject, createOpt, services)
|
||||
: await services.savedObjectsClient.create(savedObject.getEsType(), attributes, {
|
||||
...createOpt,
|
||||
overwrite: true,
|
||||
? await saveWithConfirmation(
|
||||
attributes as GraphSavedObjectAttributes,
|
||||
savedObject,
|
||||
createOpt,
|
||||
services
|
||||
)
|
||||
: savedObject.id
|
||||
? await services.contentClient.update<GraphUpdateIn, GraphUpdateOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
id: savedObject.id,
|
||||
data: {
|
||||
...(extractedRefs.attributes as GraphSavedObjectAttributes),
|
||||
},
|
||||
options: {
|
||||
references: extractedRefs.references,
|
||||
},
|
||||
})
|
||||
: await services.contentClient.create<GraphCreateIn, GraphCreateOut>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
data: attributes as GraphSavedObjectAttributes,
|
||||
options: {
|
||||
references: createOpt.references,
|
||||
overwrite: true,
|
||||
},
|
||||
});
|
||||
|
||||
savedObject.id = resp.id;
|
||||
savedObject.id = resp.item.id;
|
||||
savedObject.isSaving = false;
|
||||
savedObject.lastSavedTitle = savedObject.title;
|
||||
return savedObject.id;
|
||||
|
|
|
@ -10,9 +10,8 @@ import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks';
|
|||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { createMockGraphStore } from '../state_management/mocks';
|
||||
import { Workspace } from '../types';
|
||||
import { SavedObjectsClientCommon } from '@kbn/data-views-plugin/public';
|
||||
import { renderHook, act, RenderHookOptions } from '@testing-library/react-hooks';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const useLocation = () => ({
|
||||
|
@ -33,19 +32,19 @@ jest.mock('react-router-dom', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const mockSavedObjectsClient = {
|
||||
resolve: jest.fn().mockResolvedValue({
|
||||
saved_object: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } },
|
||||
outcome: 'exactMatch',
|
||||
const mockContentClient = {
|
||||
get: jest.fn().mockResolvedValue({
|
||||
item: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } },
|
||||
meta: { outcome: 'exactMatch' },
|
||||
}),
|
||||
find: jest.fn().mockResolvedValue({ title: 'test', perPage: 1, total: 1, page: 1 }),
|
||||
} as unknown as SavedObjectsClientCommon;
|
||||
search: jest.fn().mockResolvedValue({ title: 'test', perPage: 1, total: 1, page: 1 }),
|
||||
} as unknown as ContentClient;
|
||||
|
||||
describe('use_workspace_loader', () => {
|
||||
const defaultProps: UseWorkspaceLoaderProps = {
|
||||
workspaceRef: { current: {} as Workspace },
|
||||
store: createMockGraphStore({}).store,
|
||||
savedObjectsClient: mockSavedObjectsClient as unknown as SavedObjectsClientContract,
|
||||
contentClient: mockContentClient as unknown as ContentClient,
|
||||
coreStart: coreMock.createStart(),
|
||||
spaces: spacesPluginMock.createStartContract(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
|
@ -65,13 +64,15 @@ describe('use_workspace_loader', () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
spaces: spacesPluginMock.createStartContract(),
|
||||
savedObjectsClient: {
|
||||
...mockSavedObjectsClient,
|
||||
resolve: jest.fn().mockResolvedValue({
|
||||
saved_object: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } },
|
||||
outcome: 'aliasMatch',
|
||||
alias_target_id: 'aliasTargetId',
|
||||
alias_purpose: 'savedObjectConversion',
|
||||
contentClient: {
|
||||
...mockContentClient,
|
||||
get: jest.fn().mockResolvedValue({
|
||||
item: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } },
|
||||
meta: {
|
||||
outcome: 'aliasMatch',
|
||||
aliasTargetId: 'aliasTargetId',
|
||||
aliasPurpose: 'savedObjectConversion',
|
||||
},
|
||||
}),
|
||||
},
|
||||
} as unknown as UseWorkspaceLoaderProps;
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import type { SavedObjectsClientContract, ResolvedSimpleSavedObject } from '@kbn/core/public';
|
||||
import type { ResolvedSimpleSavedObject } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { SpacesApi } from '@kbn/spaces-plugin/public';
|
||||
import type { DataViewListItem } from '@kbn/data-views-plugin/common';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { GraphStore } from '../state_management';
|
||||
import { GraphWorkspaceSavedObject, Workspace } from '../types';
|
||||
import { getEmptyWorkspace, getSavedWorkspace } from './saved_workspace_utils';
|
||||
|
@ -21,7 +22,7 @@ import { getEditUrl } from '../services/url';
|
|||
export interface UseWorkspaceLoaderProps {
|
||||
store: GraphStore;
|
||||
workspaceRef: React.MutableRefObject<Workspace | undefined>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentClient: ContentClient;
|
||||
coreStart: CoreStart;
|
||||
spaces?: SpacesApi;
|
||||
data: DataPublicPluginStart;
|
||||
|
@ -47,7 +48,7 @@ export const useWorkspaceLoader = ({
|
|||
spaces,
|
||||
workspaceRef,
|
||||
store,
|
||||
savedObjectsClient,
|
||||
contentClient,
|
||||
data,
|
||||
}: UseWorkspaceLoaderProps) => {
|
||||
const [state, setState] = useState<WorkspaceLoadedState>();
|
||||
|
@ -85,7 +86,7 @@ export const useWorkspaceLoader = ({
|
|||
sharingSavedObjectProps?: SharingSavedObjectProps;
|
||||
}> {
|
||||
return id
|
||||
? await getSavedWorkspace(savedObjectsClient, id).catch(function (e) {
|
||||
? await getSavedWorkspace(contentClient, id).catch(function (e) {
|
||||
coreStart.notifications.toasts.addError(e, {
|
||||
title: i18n.translate('xpack.graph.missingWorkspaceErrorMessage', {
|
||||
defaultMessage: "Couldn't load graph with ID",
|
||||
|
@ -141,7 +142,7 @@ export const useWorkspaceLoader = ({
|
|||
search,
|
||||
store,
|
||||
historyReplace,
|
||||
savedObjectsClient,
|
||||
contentClient,
|
||||
setState,
|
||||
coreStart,
|
||||
workspaceRef,
|
||||
|
|
|
@ -23,17 +23,21 @@ import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/publi
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
|
||||
import {
|
||||
ContentManagementPublicSetup,
|
||||
ContentManagementPublicStart,
|
||||
} from '@kbn/content-management-plugin/public';
|
||||
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { checkLicense } from '../common/check_license';
|
||||
import { ConfigSchema } from '../config';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
|
||||
export interface GraphPluginSetupDependencies {
|
||||
home?: HomePublicPluginSetup;
|
||||
contentManagement: ContentManagementPublicSetup;
|
||||
}
|
||||
|
||||
export interface GraphPluginStartDependencies {
|
||||
|
@ -41,11 +45,11 @@ export interface GraphPluginStartDependencies {
|
|||
licensing: LicensingPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
savedObjects: SavedObjectsStart;
|
||||
inspector: InspectorPublicPluginStart;
|
||||
home?: HomePublicPluginStart;
|
||||
spaces?: SpacesApi;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
}
|
||||
|
||||
export class GraphPlugin
|
||||
|
@ -55,7 +59,10 @@ export class GraphPlugin
|
|||
|
||||
constructor(private initializerContext: PluginInitializerContext<ConfigSchema>) {}
|
||||
|
||||
setup(core: CoreSetup<GraphPluginStartDependencies>, { home }: GraphPluginSetupDependencies) {
|
||||
setup(
|
||||
core: CoreSetup<GraphPluginStartDependencies>,
|
||||
{ home, contentManagement }: GraphPluginSetupDependencies
|
||||
) {
|
||||
if (home) {
|
||||
home.featureCatalogue.register({
|
||||
id: 'graph',
|
||||
|
@ -77,6 +84,16 @@ export class GraphPlugin
|
|||
|
||||
const config = this.initializerContext.config.get();
|
||||
|
||||
contentManagement.registry.register({
|
||||
id: CONTENT_ID,
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
name: i18n.translate('xpack.graph.content.name', {
|
||||
defaultMessage: 'Graph Visualization',
|
||||
}),
|
||||
});
|
||||
|
||||
core.application.register({
|
||||
id: 'graph',
|
||||
title: 'Graph',
|
||||
|
@ -100,7 +117,7 @@ export class GraphPlugin
|
|||
navigation: pluginsStart.navigation,
|
||||
data: pluginsStart.data,
|
||||
unifiedSearch: pluginsStart.unifiedSearch,
|
||||
savedObjectsClient: coreStart.savedObjects.client,
|
||||
contentClient: pluginsStart.contentManagement.client,
|
||||
addBasePath: core.http.basePath.prepend,
|
||||
getBasePath: core.http.basePath.get,
|
||||
canEditDrillDownUrls: config.canEditDrillDownUrls,
|
||||
|
@ -111,7 +128,6 @@ export class GraphPlugin
|
|||
toastNotifications: coreStart.notifications.toasts,
|
||||
indexPatterns: pluginsStart.data!.indexPatterns,
|
||||
overlays: coreStart.overlays,
|
||||
savedObjects: pluginsStart.savedObjects,
|
||||
uiSettings: core.uiSettings,
|
||||
spaces: pluginsStart.spaces,
|
||||
inspect: pluginsStart.inspector,
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { OverlayStart, SavedObjectsClientContract } from '@kbn/core/public';
|
||||
import { OverlayStart } from '@kbn/core/public';
|
||||
import { SaveResult } from '@kbn/saved-objects-plugin/public';
|
||||
import { showSaveModal } from '@kbn/saved-objects-plugin/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { GraphWorkspaceSavedObject, GraphSavePolicy } from '../types';
|
||||
import { SaveModal, OnSaveGraphProps } from '../components/save_modal';
|
||||
|
||||
export interface SaveWorkspaceServices {
|
||||
overlays: OverlayStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentClient: ContentClient;
|
||||
}
|
||||
|
||||
export type SaveWorkspaceHandler = (
|
||||
|
|
|
@ -5,16 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
NotificationsStart,
|
||||
HttpStart,
|
||||
OverlayStart,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/public';
|
||||
import { NotificationsStart, HttpStart, OverlayStart } from '@kbn/core/public';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import { createStore, applyMiddleware, AnyAction } from 'redux';
|
||||
import { ChromeStart } from '@kbn/core/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import { GraphStoreDependencies, createRootReducer, GraphStore, GraphState } from './store';
|
||||
import { Workspace } from '../types';
|
||||
|
||||
|
@ -58,6 +54,12 @@ export function createMockGraphStore({
|
|||
} as unknown as ChromeStart,
|
||||
createWorkspace: jest.fn((index, advancedSettings) => workspaceMock),
|
||||
getWorkspace: jest.fn(() => workspaceMock),
|
||||
contentClient: {
|
||||
get: jest.fn(),
|
||||
search: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
} as unknown as ContentClient,
|
||||
indexPatternProvider: {
|
||||
get: jest.fn(async (id: string) => {
|
||||
if (id === 'missing-dataview') {
|
||||
|
@ -78,10 +80,6 @@ export function createMockGraphStore({
|
|||
overlays: {
|
||||
openModal: jest.fn(),
|
||||
} as unknown as OverlayStart,
|
||||
savedObjectsClient: {
|
||||
find: jest.fn(),
|
||||
get: jest.fn(),
|
||||
} as unknown as SavedObjectsClientContract,
|
||||
handleSearchQueryError: jest.fn(),
|
||||
...mockedDepsOverwrites,
|
||||
};
|
||||
|
|
|
@ -231,7 +231,7 @@ function showModal(
|
|||
workspace: savedWorkspace,
|
||||
saveWorkspace: saveWorkspaceHandler,
|
||||
services: {
|
||||
savedObjectsClient: deps.savedObjectsClient,
|
||||
contentClient: deps.contentClient,
|
||||
overlays: deps.overlays,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
import createSagaMiddleware, { SagaMiddleware } from 'redux-saga';
|
||||
import { combineReducers, createStore, Store, AnyAction, Dispatch, applyMiddleware } from 'redux';
|
||||
import { ChromeStart, OverlayStart, SavedObjectsClientContract } from '@kbn/core/public';
|
||||
import { ChromeStart, OverlayStart } from '@kbn/core/public';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { ContentClient } from '@kbn/content-management-plugin/public';
|
||||
import {
|
||||
fieldsReducer,
|
||||
FieldsState,
|
||||
|
@ -46,7 +47,7 @@ export interface GraphStoreDependencies {
|
|||
notifications: CoreStart['notifications'];
|
||||
http: CoreStart['http'];
|
||||
overlays: OverlayStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentClient: ContentClient;
|
||||
savePolicy: GraphSavePolicy;
|
||||
changeUrl: (newUrl: string) => void;
|
||||
notifyReact: () => void;
|
||||
|
|
328
x-pack/plugins/graph/server/content_management/graph_storage.ts
Normal file
328
x-pack/plugins/graph/server/content_management/graph_storage.ts
Normal file
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import Boom from '@hapi/boom';
|
||||
import type { SearchQuery } from '@kbn/content-management-plugin/common';
|
||||
import type { ContentStorage, StorageContext } from '@kbn/content-management-plugin/server';
|
||||
import type {
|
||||
SavedObject,
|
||||
SavedObjectReference,
|
||||
SavedObjectsFindOptions,
|
||||
} from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import { cmServicesDefinition } from '../../common/content_management/cm_services';
|
||||
import type {
|
||||
GraphSavedObjectAttributes,
|
||||
GraphSavedObject,
|
||||
PartialGraphSavedObject,
|
||||
GraphGetOut,
|
||||
GraphCreateIn,
|
||||
GraphCreateOut,
|
||||
CreateOptions,
|
||||
GraphUpdateIn,
|
||||
GraphUpdateOut,
|
||||
UpdateOptions,
|
||||
GraphDeleteOut,
|
||||
GraphSearchQuery,
|
||||
GraphSearchOut,
|
||||
} from '../../common/content_management';
|
||||
|
||||
const savedObjectClientFromRequest = async (ctx: StorageContext) => {
|
||||
if (!ctx.requestHandlerContext) {
|
||||
throw new Error('Storage context.requestHandlerContext missing.');
|
||||
}
|
||||
|
||||
const { savedObjects } = await ctx.requestHandlerContext.core;
|
||||
return savedObjects.client;
|
||||
};
|
||||
|
||||
type PartialSavedObject<T> = Omit<SavedObject<Partial<T>>, 'references'> & {
|
||||
references: SavedObjectReference[] | undefined;
|
||||
};
|
||||
|
||||
function savedObjectToGraphSavedObject(
|
||||
savedObject: SavedObject<GraphSavedObjectAttributes>,
|
||||
partial: false
|
||||
): GraphSavedObject;
|
||||
|
||||
function savedObjectToGraphSavedObject(
|
||||
savedObject: PartialSavedObject<GraphSavedObjectAttributes>,
|
||||
partial: true
|
||||
): PartialGraphSavedObject;
|
||||
|
||||
function savedObjectToGraphSavedObject(
|
||||
savedObject:
|
||||
| SavedObject<GraphSavedObjectAttributes>
|
||||
| PartialSavedObject<GraphSavedObjectAttributes>
|
||||
): GraphSavedObject | PartialGraphSavedObject {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
updated_at: updatedAt,
|
||||
created_at: createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
version,
|
||||
kibanaSavedObjectMeta,
|
||||
wsState,
|
||||
numVertices,
|
||||
numLinks,
|
||||
legacyIndexPatternRef,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
} = savedObject;
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
kibanaSavedObjectMeta,
|
||||
wsState,
|
||||
version,
|
||||
numLinks,
|
||||
numVertices,
|
||||
legacyIndexPatternRef,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
};
|
||||
}
|
||||
|
||||
const SO_TYPE = 'graph-workspace';
|
||||
|
||||
export class GraphStorage implements ContentStorage<GraphSavedObject, PartialGraphSavedObject> {
|
||||
constructor() {}
|
||||
|
||||
async get(ctx: StorageContext, id: string): Promise<GraphGetOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Save data in DB
|
||||
const {
|
||||
saved_object: savedObject,
|
||||
alias_purpose: aliasPurpose,
|
||||
alias_target_id: aliasTargetId,
|
||||
outcome,
|
||||
} = await soClient.resolve<GraphSavedObjectAttributes>(SO_TYPE, id);
|
||||
|
||||
const response: GraphGetOut = {
|
||||
item: savedObjectToGraphSavedObject(savedObject, false),
|
||||
meta: {
|
||||
aliasPurpose,
|
||||
aliasTargetId,
|
||||
outcome,
|
||||
},
|
||||
};
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.get.out.result.down<GraphGetOut, GraphGetOut>(
|
||||
response
|
||||
);
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async bulkGet(): Promise<never> {
|
||||
// Not implemented. Graph does not use bulkGet
|
||||
throw new Error(`[bulkGet] has not been implemented. See GraphStorage class.`);
|
||||
}
|
||||
|
||||
async create(
|
||||
ctx: StorageContext,
|
||||
data: GraphCreateIn['data'],
|
||||
options: CreateOptions
|
||||
): Promise<GraphCreateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.create.in.data.up<
|
||||
GraphSavedObjectAttributes,
|
||||
GraphSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const savedObject = await soClient.create<GraphSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.create.out.result.down<
|
||||
GraphCreateOut,
|
||||
GraphCreateOut
|
||||
>({
|
||||
item: savedObjectToGraphSavedObject(savedObject, false),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async update(
|
||||
ctx: StorageContext,
|
||||
id: string,
|
||||
data: GraphUpdateIn['data'],
|
||||
options: UpdateOptions
|
||||
): Promise<GraphUpdateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.update.in.data.up<
|
||||
GraphSavedObjectAttributes,
|
||||
GraphSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const partialSavedObject = await soClient.update<GraphSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
id,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.update.out.result.down<
|
||||
GraphUpdateOut,
|
||||
GraphUpdateOut
|
||||
>({
|
||||
item: savedObjectToGraphSavedObject(partialSavedObject, true),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async delete(ctx: StorageContext, id: string): Promise<GraphDeleteOut> {
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
await soClient.delete(SO_TYPE, id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async search(
|
||||
ctx: StorageContext,
|
||||
query: SearchQuery,
|
||||
options: GraphSearchQuery = {}
|
||||
): Promise<GraphSearchOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Validate and UP transform the options
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up<
|
||||
GraphSearchQuery,
|
||||
GraphSearchQuery
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid payload. ${optionsError.message}`);
|
||||
}
|
||||
const { searchFields = ['title^3', 'description'], types = ['graph-workspace'] } =
|
||||
optionsToLatest;
|
||||
|
||||
const { included, excluded } = query.tags ?? {};
|
||||
const hasReference: SavedObjectsFindOptions['hasReference'] = included
|
||||
? included.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const hasNoReference: SavedObjectsFindOptions['hasNoReference'] = excluded
|
||||
? excluded.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const soQuery: SavedObjectsFindOptions = {
|
||||
type: types,
|
||||
search: query.text,
|
||||
perPage: query.limit,
|
||||
page: query.cursor ? Number(query.cursor) : undefined,
|
||||
defaultSearchOperator: 'AND',
|
||||
searchFields,
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
};
|
||||
|
||||
// Execute the query in the DB
|
||||
const response = await soClient.find<GraphSavedObjectAttributes>(soQuery);
|
||||
|
||||
// Validate the response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.search.out.result.down<
|
||||
GraphSearchOut,
|
||||
GraphSearchOut
|
||||
>({
|
||||
hits: response.saved_objects.map((so) => savedObjectToGraphSavedObject(so, false)),
|
||||
pagination: {
|
||||
total: response.total,
|
||||
},
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
8
x-pack/plugins/graph/server/content_management/index.ts
Normal file
8
x-pack/plugins/graph/server/content_management/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { GraphStorage } from './graph_storage';
|
|
@ -11,11 +11,14 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
|
|||
import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server';
|
||||
import { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
|
||||
import { ContentManagementServerSetup } from '@kbn/content-management-plugin/server';
|
||||
import { LicenseState } from './lib/license_state';
|
||||
import { registerSearchRoute } from './routes/search';
|
||||
import { registerExploreRoute } from './routes/explore';
|
||||
import { registerSampleData } from './sample_data';
|
||||
import { graphWorkspace } from './saved_objects';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import { GraphStorage } from './content_management/graph_storage';
|
||||
|
||||
export class GraphPlugin implements Plugin {
|
||||
private licenseState: LicenseState | null = null;
|
||||
|
@ -26,10 +29,12 @@ export class GraphPlugin implements Plugin {
|
|||
licensing,
|
||||
home,
|
||||
features,
|
||||
contentManagement,
|
||||
}: {
|
||||
licensing: LicensingPluginSetup;
|
||||
home?: HomeServerPluginSetup;
|
||||
features?: FeaturesPluginSetup;
|
||||
contentManagement: ContentManagementServerSetup;
|
||||
}
|
||||
) {
|
||||
const licenseState = new LicenseState();
|
||||
|
@ -38,6 +43,14 @@ export class GraphPlugin implements Plugin {
|
|||
core.savedObjects.registerType(graphWorkspace);
|
||||
licensing.featureUsage.register('Graph', 'platinum');
|
||||
|
||||
contentManagement.register({
|
||||
id: CONTENT_ID,
|
||||
storage: new GraphStorage(),
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
});
|
||||
|
||||
if (home) {
|
||||
registerSampleData(home.sampleData, licenseState);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
"@kbn/saved-objects-management-plugin",
|
||||
"@kbn/saved-objects-finder-plugin",
|
||||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/object-versioning",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue