From dbec0b10074ff694c6d47556c631e75688b4ff80 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 13 Dec 2019 14:48:03 +0100 Subject: [PATCH] [Graph] Deangularize saved object handling (workspace) (#52529) --- .../angular/services/saved_workspace.js | 67 -------------- .../angular/services/saved_workspace.ts | 65 ++++++++++++++ ....js => saved_workspace_references.test.ts} | 19 ++-- ...ences.js => saved_workspace_references.ts} | 21 +++-- .../angular/services/saved_workspaces.js | 90 ------------------- .../angular/services/saved_workspaces.ts | 76 ++++++++++++++++ 6 files changed, 168 insertions(+), 170 deletions(-) delete mode 100644 x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js create mode 100644 x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts rename x-pack/legacy/plugins/graph/public/angular/services/{saved_workspace_references.test.js => saved_workspace_references.test.ts} (91%) rename x-pack/legacy/plugins/graph/public/angular/services/{saved_workspace_references.js => saved_workspace_references.ts} (73%) delete mode 100644 x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js create mode 100644 x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js deleted file mode 100644 index 444e68dd03520..0000000000000 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -import { i18n } from '@kbn/i18n'; -import { - extractReferences, - injectReferences, -} from './saved_workspace_references'; - -export function SavedWorkspaceProvider(Private) { - // SavedWorkspace constructor. Usually you'd interact with an instance of this. - // ID is option, without it one will be generated on save. - const SavedObject = Private(SavedObjectProvider); - class SavedWorkspace extends SavedObject { - constructor(id) { - // Gives our SavedWorkspace the properties of a SavedObject - super ({ - type: SavedWorkspace.type, - mapping: SavedWorkspace.mapping, - searchSource: SavedWorkspace.searchsource, - extractReferences: extractReferences, - injectReferences: injectReferences, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id: id, - - // default values that will get assigned if the doc is new - defaults: { - title: i18n.translate('xpack.graph.savedWorkspace.workspaceNameTitle', { - defaultMessage: 'New Graph Workspace' - }), - numLinks: 0, - numVertices: 0, - wsState: '{}', - version: 1 - } - - }); - - // Overwrite the default getDisplayName function which uses type and which is not very - // user friendly for this object. - this.getDisplayName = function () { - return 'graph workspace'; - }; - } - - } //End of class - - SavedWorkspace.type = 'graph-workspace'; - - // if type:workspace has no mapping, we push this mapping into ES - SavedWorkspace.mapping = { - title: 'text', - description: 'text', - numLinks: 'integer', - numVertices: 'integer', - version: 'integer', - wsState: 'json' - }; - - SavedWorkspace.searchsource = false; - return SavedWorkspace; -} diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts new file mode 100644 index 0000000000000..bcde72a02f02e --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; +import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; +import { i18n } from '@kbn/i18n'; +import { extractReferences, injectReferences } from './saved_workspace_references'; + +export interface SavedWorkspace extends SavedObject { + wsState?: string; +} + +export function createSavedWorkspaceClass(services: SavedObjectKibanaServices) { + // SavedWorkspace constructor. Usually you'd interact with an instance of this. + // ID is option, without it one will be generated on save. + const SavedObjectClass = createSavedObjectClass(services); + class SavedWorkspaceClass extends SavedObjectClass { + public static type: string = 'graph-workspace'; + // if type:workspace has no mapping, we push this mapping into ES + public static mapping: Record = { + title: 'text', + description: 'text', + numLinks: 'integer', + numVertices: 'integer', + version: 'integer', + wsState: 'json', + }; + // Order these fields to the top, the rest are alphabetical + public static fieldOrder = ['title', 'description']; + public static searchSource = false; + + public wsState?: string; + + constructor(id: string) { + // Gives our SavedWorkspace the properties of a SavedObject + super({ + type: SavedWorkspaceClass.type, + mapping: SavedWorkspaceClass.mapping, + searchSource: SavedWorkspaceClass.searchSource, + extractReferences, + injectReferences, + // if this is null/undefined then the SavedObject will be assigned the defaults + id, + // default values that will get assigned if the doc is new + defaults: { + title: i18n.translate('xpack.graph.savedWorkspace.workspaceNameTitle', { + defaultMessage: 'New Graph Workspace', + }), + numLinks: 0, + numVertices: 0, + wsState: '{}', + version: 1, + }, + }); + } + // Overwrite the default getDisplayName function which uses type and which is not very + // user friendly for this object. + getDisplayName = () => { + return 'graph workspace'; + }; + } + return SavedWorkspaceClass; +} diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.js b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.ts similarity index 91% rename from x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.js rename to x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.ts index 01eb7f9ead1f0..716520cb83aa1 100644 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.js +++ b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.ts @@ -5,6 +5,7 @@ */ import { extractReferences, injectReferences } from './saved_workspace_references'; +import { SavedWorkspace } from './saved_workspace'; describe('extractReferences', () => { test('extracts references from wsState', () => { @@ -19,6 +20,7 @@ describe('extractReferences', () => { }) ), }, + references: [], }; const updatedDoc = extractReferences(doc); expect(updatedDoc).toMatchInlineSnapshot(` @@ -48,6 +50,7 @@ Object { }) ), }, + references: [], }; expect(() => extractReferences(doc)).toThrowErrorMatchingInlineSnapshot( `"indexPattern attribute is missing in \\"wsState\\""` @@ -59,12 +62,12 @@ describe('injectReferences', () => { test('injects references into context', () => { const context = { id: '1', - foo: true, + title: 'test', wsState: JSON.stringify({ indexPatternRefName: 'indexPattern_0', bar: true, }), - }; + } as SavedWorkspace; const references = [ { name: 'indexPattern_0', @@ -75,8 +78,8 @@ describe('injectReferences', () => { injectReferences(context, references); expect(context).toMatchInlineSnapshot(` Object { - "foo": true, "id": "1", + "title": "test", "wsState": "{\\"bar\\":true,\\"indexPattern\\":\\"pattern*\\"}", } `); @@ -85,13 +88,13 @@ Object { test('skips when wsState is not a string', () => { const context = { id: '1', - foo: true, - }; + title: 'test', + } as SavedWorkspace; injectReferences(context, []); expect(context).toMatchInlineSnapshot(` Object { - "foo": true, "id": "1", + "title": "test", } `); }); @@ -100,7 +103,7 @@ Object { const context = { id: '1', wsState: JSON.stringify({ bar: true }), - }; + } as SavedWorkspace; injectReferences(context, []); expect(context).toMatchInlineSnapshot(` Object { @@ -116,7 +119,7 @@ Object { wsState: JSON.stringify({ indexPatternRefName: 'indexPattern_0', }), - }; + } as SavedWorkspace; expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( `"Could not find reference \\"indexPattern_0\\""` ); diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.js b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.ts similarity index 73% rename from x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.js rename to x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.ts index a1b4254685c40..3a596b8068655 100644 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.js +++ b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.ts @@ -4,9 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -export function extractReferences({ attributes, references = [] }) { +import { SavedObjectAttributes, SavedObjectReference } from 'kibana/public'; +import { SavedWorkspace } from './saved_workspace'; + +export function extractReferences({ + attributes, + references = [], +}: { + attributes: SavedObjectAttributes; + references: SavedObjectReference[]; +}) { // For some reason, wsState comes in stringified 2x - const state = JSON.parse(JSON.parse(attributes.wsState)); + const state = JSON.parse(JSON.parse(String(attributes.wsState))); const { indexPattern } = state; if (!indexPattern) { throw new Error('indexPattern attribute is missing in "wsState"'); @@ -20,7 +29,7 @@ export function extractReferences({ attributes, references = [] }) { name: 'indexPattern_0', type: 'index-pattern', id: indexPattern, - } + }, ], attributes: { ...attributes, @@ -29,7 +38,7 @@ export function extractReferences({ attributes, references = [] }) { }; } -export function injectReferences(savedObject, references) { +export function injectReferences(savedObject: SavedWorkspace, references: SavedObjectReference[]) { // Skip if wsState is missing, at the time of development of this, there is no guarantee each // saved object has wsState. if (typeof savedObject.wsState !== 'string') { @@ -41,7 +50,9 @@ export function injectReferences(savedObject, references) { if (!state.indexPatternRefName) { return; } - const indexPatternReference = references.find(reference => reference.name === state.indexPatternRefName); + const indexPatternReference = references.find( + reference => reference.name === state.indexPatternRefName + ); if (!indexPatternReference) { // Throw an error as "indexPatternRefName" means the reference exists within // "references" and in this scenario we have bad data. diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js deleted file mode 100644 index 1fef4b7c38c07..0000000000000 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; - -import chrome from 'ui/chrome'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { i18n } from '@kbn/i18n'; - -import { SavedWorkspaceProvider } from './saved_workspace'; - - -export function SavedWorkspacesProvider(kbnUrl, Private, Promise) { - const savedObjectsClient = Private(SavedObjectsClientProvider); - const SavedWorkspace = Private(SavedWorkspaceProvider); - - this.type = SavedWorkspace.type; - this.Class = SavedWorkspace; - - this.loaderProperties = { - name: 'Graph workspace', - noun: i18n.translate('xpack.graph.savedWorkspaces.graphWorkspaceLabel', { - defaultMessage: 'Graph workspace' - }), - nouns: i18n.translate('xpack.graph.savedWorkspaces.graphWorkspacesLabel', { - defaultMessage: 'Graph workspaces' - }) - }; - - // Returns a single dashboard by ID, should be the name of the workspace - this.get = function (id) { - // Returns a promise that contains a workspace which is a subclass of docSource - return (new SavedWorkspace(id)).init(); - }; - - this.urlFor = function (id) { - return chrome.addBasePath(kbnUrl.eval('/app/graph#/workspace/{{id}}', { id })); - }; - - this.delete = function (ids) { - ids = !_.isArray(ids) ? [ids] : ids; - return Promise.map(ids, function (id) { - return (new SavedWorkspace(id)).delete(); - }); - }; - - this.mapHits = function (hit) { - const source = hit.attributes; - source.id = hit.id; - source.url = this.urlFor(hit.id); - source.icon = 'fa-share-alt';// looks like a graph - return source; - }; - - this.find = function (searchString, size = 100) { - let body; - if (searchString) { - body = { - query: { - simple_query_string: { - query: searchString + '*', - fields: ['title^3', 'description'], - default_operator: 'AND' - } - } - }; - } else { - body = { query: { match_all: {} } }; - } - - return savedObjectsClient.find({ - type: SavedWorkspace.type, - search: searchString ? `${searchString}*` : undefined, - perPage: size, - searchFields: ['title^3', 'description'] - }) - .then(resp => { - return { - total: resp.total, - hits: resp.savedObjects.map((hit) => this.mapHits(hit)) - }; - }); - }; -} - -SavedObjectRegistryProvider.register(SavedWorkspacesProvider); diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts new file mode 100644 index 0000000000000..e28bb60fb466b --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { i18n } from '@kbn/i18n'; + +import { createSavedWorkspaceClass } from './saved_workspace'; + +export function SavedWorkspacesProvider() { + const savedObjectsClient = npStart.core.savedObjects.client; + const services = { + savedObjectsClient, + indexPatterns: npStart.plugins.data.indexPatterns, + chrome: npStart.core.chrome, + overlays: npStart.core.overlays, + }; + + const SavedWorkspace = createSavedWorkspaceClass(services); + const urlFor = (id: string) => + npSetup.core.http.basePath.prepend(`/app/graph#/workspace/${encodeURIComponent(id)}`); + const mapHits = (hit: { id: string; attributes: Record }) => { + const source = hit.attributes; + source.id = hit.id; + source.url = urlFor(hit.id); + source.icon = 'fa-share-alt'; // looks like a graph + return source; + }; + + return { + type: SavedWorkspace.type, + Class: SavedWorkspace, + loaderProperties: { + name: 'Graph workspace', + noun: i18n.translate('xpack.graph.savedWorkspaces.graphWorkspaceLabel', { + defaultMessage: 'Graph workspace', + }), + nouns: i18n.translate('xpack.graph.savedWorkspaces.graphWorkspacesLabel', { + defaultMessage: 'Graph workspaces', + }), + }, + // Returns a single dashboard by ID, should be the name of the workspace + get: (id: string) => { + // Returns a promise that contains a workspace which is a subclass of docSource + // @ts-ignore + return new SavedWorkspace(id).init(); + }, + urlFor, + delete: (ids: string | string[]) => { + const idArr = Array.isArray(ids) ? ids : [ids]; + return Promise.all( + idArr.map((id: string) => savedObjectsClient.delete(SavedWorkspace.type, id)) + ); + }, + find: (searchString: string, size: number = 100) => { + return savedObjectsClient + .find({ + type: SavedWorkspace.type, + search: searchString ? `${searchString}*` : undefined, + perPage: size, + searchFields: ['title^3', 'description'], + }) + .then(resp => { + return { + total: resp.total, + hits: resp.savedObjects.map(hit => mapHits(hit)), + }; + }); + }, + }; +} + +SavedObjectRegistryProvider.register(SavedWorkspacesProvider);