diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ab05b32ab063e..c32bf8cbaa1c3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,6 +13,7 @@ /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/plugins/vis_type_xy/ @elastic/kibana-app +/src/plugins/vis_type_table/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/vis_type_timelion/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app diff --git a/.i18nrc.json b/.i18nrc.json index 4a516f23ebf05..d4286a7bd50e0 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -48,7 +48,7 @@ "visDefaultEditor": "src/plugins/vis_default_editor", "visTypeMarkdown": "src/plugins/vis_type_markdown", "visTypeMetric": "src/plugins/vis_type_metric", - "visTypeTable": "src/legacy/core_plugins/vis_type_table", + "visTypeTable": "src/plugins/vis_type_table", "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": ["src/legacy/core_plugins/vis_type_timeseries", "src/plugins/vis_type_timeseries"], "visTypeVega": "src/legacy/core_plugins/vis_type_vega", diff --git a/docs/images/clone_panel.gif b/docs/images/clone_panel.gif new file mode 100644 index 0000000000000..e521e438d051a Binary files /dev/null and b/docs/images/clone_panel.gif differ diff --git a/docs/setup/production.asciidoc b/docs/setup/production.asciidoc index eef2b11e53d85..19f9d64d13623 100644 --- a/docs/setup/production.asciidoc +++ b/docs/setup/production.asciidoc @@ -133,7 +133,8 @@ server.port Settings that must be the same: -------- xpack.security.encryptionKey //decrypting session cookies -xpack.reporting.encryptionKey //decrypting reports stored in Elasticsearch +xpack.reporting.encryptionKey //decrypting reports +xpack.encryptedSavedObjects.encryptionKey // decrypting saved objects -------- Separate configuration files can be used from the command line by using the `-c` flag: diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc index ab529a533d5e3..de714ae40086b 100644 --- a/docs/user/dashboard.asciidoc +++ b/docs/user/dashboard.asciidoc @@ -98,6 +98,24 @@ to the new dimensions. * To delete a panel, open the panel menu and select *Delete from dashboard.* Deleting a panel from a dashboard does *not* delete the saved visualization or search. +[float] +[[cloning-a-panel]] +=== Clone dashboard elements + +In *Edit* mode, you can clone any panel on a dashboard. + +To clone an existing panel, open the panel menu of the element you wish to clone, then select *Clone panel*. + +* Cloned panels appear beside the original, and will move other panels down to make room if necessary. + +* Clones support all of the original panel's functionality, including renaming, editing, and cloning. + +* All cloned visualizations will appear in the visualization list. + +[role="screenshot"] +image:images/clone_panel.gif[clone panel] + + [float] [[viewing-detailed-information]] === Inspect and edit elements diff --git a/examples/state_containers_examples/public/todo/app.tsx b/examples/state_containers_examples/public/todo/app.tsx index 319680d07f9bc..f2183613e4a12 100644 --- a/examples/state_containers_examples/public/todo/app.tsx +++ b/examples/state_containers_examples/public/todo/app.tsx @@ -20,7 +20,7 @@ import { AppMountParameters } from 'kibana/public'; import ReactDOM from 'react-dom'; import React from 'react'; -import { createHashHistory, createBrowserHistory } from 'history'; +import { createHashHistory } from 'history'; import { TodoAppPage } from './todo'; export interface AppOptions { @@ -35,13 +35,10 @@ export enum History { } export const renderApp = ( - { appBasePath, element }: AppMountParameters, + { appBasePath, element, history: platformHistory }: AppMountParameters, { appInstanceId, appTitle, historyType }: AppOptions ) => { - const history = - historyType === History.Browser - ? createBrowserHistory({ basename: appBasePath }) - : createHashHistory(); + const history = historyType === History.Browser ? platformHistory : createHashHistory(); ReactDOM.render( = ({ filter }) => { return ( <>
- + All - + Completed - + Not Completed @@ -121,6 +124,7 @@ const TodoApp: React.FC = ({ filter }) => { }); }} label={todo.text} + data-test-subj={`todoCheckbox-${todo.id}`} /> { - const history = createBrowserHistory({ basename: appBasePath }); const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history }); ReactDOM.render( diff --git a/packages/kbn-config-schema/src/types/object_type.test.ts b/packages/kbn-config-schema/src/types/object_type.test.ts index 47a0f5f7a5491..5ab59d1c02077 100644 --- a/packages/kbn-config-schema/src/types/object_type.test.ts +++ b/packages/kbn-config-schema/src/types/object_type.test.ts @@ -18,6 +18,7 @@ */ import { schema } from '..'; +import { TypeOf } from './object_type'; test('returns value by default', () => { const type = schema.object({ @@ -350,3 +351,26 @@ test('unknowns = `ignore` affects only own keys', () => { }) ).toThrowErrorMatchingInlineSnapshot(`"[foo.baz]: definition for this key is missing"`); }); + +test('handles optional properties', () => { + const type = schema.object({ + required: schema.string(), + optional: schema.maybe(schema.string()), + }); + + type SchemaType = TypeOf; + + let foo: SchemaType = { + required: 'foo', + }; + foo = { + required: 'hello', + optional: undefined, + }; + foo = { + required: 'hello', + optional: 'bar', + }; + + expect(foo).toBeDefined(); +}); diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index 5a50e714a5931..fee2d02c1bfb9 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -26,9 +26,26 @@ export type Props = Record>; export type TypeOf> = RT['type']; +type OptionalProperties = Pick< + Base, + { + [Key in keyof Base]: undefined extends TypeOf ? Key : never; + }[keyof Base] +>; + +type RequiredProperties = Pick< + Base, + { + [Key in keyof Base]: undefined extends TypeOf ? never : Key; + }[keyof Base] +>; + // Because of https://github.com/Microsoft/TypeScript/issues/14041 // this might not have perfect _rendering_ output, but it will be typed. -export type ObjectResultType

= Readonly<{ [K in keyof P]: TypeOf }>; +export type ObjectResultType

= Readonly< + { [K in keyof OptionalProperties

]?: TypeOf } & + { [K in keyof RequiredProperties

]: TypeOf } +>; interface UnknownOptions { /** @@ -40,9 +57,7 @@ interface UnknownOptions { unknowns?: 'allow' | 'ignore' | 'forbid'; } -export type ObjectTypeOptions

= TypeOptions< - { [K in keyof P]: TypeOf } -> & +export type ObjectTypeOptions

= TypeOptions> & UnknownOptions; export class ObjectType

extends Type> { diff --git a/scripts/prettier_on_changed.js b/scripts/prettier_on_changed.js new file mode 100644 index 0000000000000..f9598110f91fd --- /dev/null +++ b/scripts/prettier_on_changed.js @@ -0,0 +1,21 @@ +/* + * 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. + */ + +require('../src/setup_node_env/babel_register'); +require('../src/dev/run_prettier_on_changed'); diff --git a/src/core/server/http/router/validator/validator.ts b/src/core/server/http/router/validator/validator.ts index 6c766e69f0f37..a2299b47ae253 100644 --- a/src/core/server/http/router/validator/validator.ts +++ b/src/core/server/http/router/validator/validator.ts @@ -17,7 +17,14 @@ * under the License. */ -import { ValidationError, Type, schema, ObjectType, isConfigSchema } from '@kbn/config-schema'; +import { + ValidationError, + Type, + schema, + ObjectType, + TypeOf, + isConfigSchema, +} from '@kbn/config-schema'; import { Stream } from 'stream'; import { RouteValidationError } from './validator_error'; @@ -85,7 +92,7 @@ type RouteValidationResultType | undefined> = T extends RouteValidationFunction ? ReturnType['value'] : T extends Type - ? ReturnType + ? TypeOf : undefined >; @@ -170,7 +177,7 @@ export class RouteValidator

{ * @internal */ public getParams(data: unknown, namespace?: string): Readonly

{ - return this.validate(this.config.params, this.options.unsafe?.params, data, namespace); + return this.validate(this.config.params, this.options.unsafe?.params, data, namespace) as P; } /** @@ -178,7 +185,7 @@ export class RouteValidator

{ * @internal */ public getQuery(data: unknown, namespace?: string): Readonly { - return this.validate(this.config.query, this.options.unsafe?.query, data, namespace); + return this.validate(this.config.query, this.options.unsafe?.query, data, namespace) as Q; } /** @@ -186,7 +193,7 @@ export class RouteValidator

{ * @internal */ public getBody(data: unknown, namespace?: string): Readonly { - return this.validate(this.config.body, this.options.unsafe?.body, data, namespace); + return this.validate(this.config.body, this.options.unsafe?.body, data, namespace) as B; } /** diff --git a/src/dev/eslint/index.js b/src/dev/eslint/index.ts similarity index 100% rename from src/dev/eslint/index.js rename to src/dev/eslint/index.ts diff --git a/src/dev/eslint/lint_files.js b/src/dev/eslint/lint_files.ts similarity index 90% rename from src/dev/eslint/lint_files.js rename to src/dev/eslint/lint_files.ts index a76edeb2eb865..80c493233f39a 100644 --- a/src/dev/eslint/lint_files.js +++ b/src/dev/eslint/lint_files.ts @@ -19,7 +19,8 @@ import { CLIEngine } from 'eslint'; -import { createFailError } from '@kbn/dev-utils'; +import { createFailError, ToolingLog } from '@kbn/dev-utils'; +import { File } from '../file'; import { REPO_ROOT } from '../constants'; /** @@ -30,7 +31,7 @@ import { REPO_ROOT } from '../constants'; * @param {Array} files * @return {undefined} */ -export function lintFiles(log, files, { fix } = {}) { +export function lintFiles(log: ToolingLog, files: File[], { fix }: { fix?: boolean } = {}) { const cli = new CLIEngine({ cache: true, cwd: REPO_ROOT, diff --git a/src/dev/eslint/pick_files_to_lint.js b/src/dev/eslint/pick_files_to_lint.ts similarity index 88% rename from src/dev/eslint/pick_files_to_lint.js rename to src/dev/eslint/pick_files_to_lint.ts index e3212c00d9e0d..b96781fc3a611 100644 --- a/src/dev/eslint/pick_files_to_lint.js +++ b/src/dev/eslint/pick_files_to_lint.ts @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - import { CLIEngine } from 'eslint'; +import { ToolingLog } from '@kbn/dev-utils'; +import { File } from '../file'; + /** * Filters a list of files to only include lintable files. * @@ -26,8 +28,8 @@ import { CLIEngine } from 'eslint'; * @param {Array} files * @return {Array} */ -export function pickFilesToLint(log, files) { - const cli = new CLIEngine(); +export function pickFilesToLint(log: ToolingLog, files: File[]) { + const cli = new CLIEngine({}); return files.filter(file => { if (!file.isJs() && !file.isTypescript()) { diff --git a/src/dev/run_prettier_on_changed.ts b/src/dev/run_prettier_on_changed.ts new file mode 100644 index 0000000000000..deca4fa1be3ce --- /dev/null +++ b/src/dev/run_prettier_on_changed.ts @@ -0,0 +1,87 @@ +/* + * 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 execa from 'execa'; +// @ts-ignore +import SimpleGit from 'simple-git'; +import { run } from '@kbn/dev-utils'; +import dedent from 'dedent'; +import Util from 'util'; + +import pkg from '../../package.json'; +import { REPO_ROOT } from './constants'; +import { File } from './file'; +import * as Eslint from './eslint'; + +run(async function getChangedFiles({ log }) { + const simpleGit = new SimpleGit(REPO_ROOT); + + const getStatus = Util.promisify(simpleGit.status.bind(simpleGit)); + const gitStatus = await getStatus(); + + if (gitStatus.files.length > 0) { + throw new Error( + dedent(`You should run prettier formatter on a clean branch. + Found not committed changes to: + ${gitStatus.files.map((f: { path: string }) => f.path).join('\n')}`) + ); + } + + const revParse = Util.promisify(simpleGit.revparse.bind(simpleGit)); + const currentBranch = await revParse(['--abbrev-ref', 'HEAD']); + const headBranch = pkg.branch; + + const diff = Util.promisify(simpleGit.diff.bind(simpleGit)); + + const changedFileStatuses: string = await diff([ + '--name-status', + `${headBranch}...${currentBranch}`, + ]); + + const changedFiles = changedFileStatuses + .split('\n') + // Ignore blank lines + .filter(line => line.trim().length > 0) + // git diff --name-status outputs lines with two OR three parts + // separated by a tab character + .map(line => line.trim().split('\t')) + .map(([status, ...paths]) => { + // ignore deleted files + if (status === 'D') { + return undefined; + } + + // the status is always in the first column + // .. If the file is edited the line will only have two columns + // .. If the file is renamed it will have three columns + // .. In any case, the last column is the CURRENT path to the file + return new File(paths[paths.length - 1]); + }) + .filter((file): file is File => Boolean(file)); + + const pathsToLint = Eslint.pickFilesToLint(log, changedFiles).map(f => f.getAbsolutePath()); + + if (pathsToLint.length > 0) { + log.debug('[prettier] run on %j files: ', pathsToLint.length, pathsToLint); + } + + while (pathsToLint.length > 0) { + await execa('npx', ['prettier@2.0.4', '--write', ...pathsToLint.splice(0, 100)]); + } +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js similarity index 94% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js index a23407a599ae2..b212ecf578dd1 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js @@ -22,11 +22,18 @@ import moment from 'moment'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import sinon from 'sinon'; -import { npStart } from '../../legacy_imports'; +import './legacy'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { npStart } from 'ui/new_platform'; import { round } from 'lodash'; -import { getAngularModule } from '../../get_inner_angular'; -import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInnerAngular } from '../../../../../../plugins/vis_type_table/public/get_inner_angular'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { initTableVisLegacyModule } from '../../../../../../plugins/vis_type_table/public/table_vis_legacy_module'; import { tabifiedData } from './tabified_data'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { configureAppAngularModule } from '../../../../../../plugins/kibana_legacy/public/angular'; describe('Table Vis - AggTable Directive', function() { let $rootScope; @@ -34,7 +41,8 @@ describe('Table Vis - AggTable Directive', function() { let settings; const initLocalAngular = () => { - const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + const tableVisModule = getInnerAngular('kibana/table_vis', npStart.core); + configureAppAngularModule(tableVisModule, npStart.core, true); initTableVisLegacyModule(tableVisModule); }; diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js similarity index 81% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js index 40a0993ccb017..3cd7de393d66a 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js @@ -20,17 +20,24 @@ import $ from 'jquery'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { npStart } from '../../legacy_imports'; -import { getAngularModule } from '../../get_inner_angular'; -import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; +import './legacy'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInnerAngular } from '../../../../../../plugins/vis_type_table/public/get_inner_angular'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { initTableVisLegacyModule } from '../../../../../../plugins/vis_type_table/public/table_vis_legacy_module'; import { tabifiedData } from './tabified_data'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { npStart } from 'ui/new_platform'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { configureAppAngularModule } from '../../../../../../plugins/kibana_legacy/public/angular'; describe('Table Vis - AggTableGroup Directive', function() { let $rootScope; let $compile; const initLocalAngular = () => { - const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + const tableVisModule = getInnerAngular('kibana/table_vis', npStart.core); + configureAppAngularModule(tableVisModule, npStart.core, true); initTableVisLegacyModule(tableVisModule); }; diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy.ts b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts similarity index 69% rename from src/legacy/core_plugins/vis_type_table/public/legacy.ts rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts index 3d5f8c1b3efe9..c6467a5beae68 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy.ts +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts @@ -16,19 +16,23 @@ * specific language governing permissions and limitations * under the License. */ - import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from './legacy_imports'; -import { plugin } from '.'; - -import { TablePluginSetupDependencies } from './plugin'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { npStart, npSetup } from 'ui/new_platform'; +import { + TableVisPlugin, + TablePluginSetupDependencies, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/vis_type_table/public/plugin'; const plugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: npSetup.plugins.visualizations, }; -const pluginInstance = plugin({} as PluginInitializerContext); +const pluginInstance = new TableVisPlugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, plugins); -export const start = pluginInstance.start(npStart.core, { data: npStart.plugins.data }); +export const start = pluginInstance.start(npStart.core, { + data: npStart.plugins.data, +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/tabified_data.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/tabified_data.js similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/tabified_data.js rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/tabified_data.js diff --git a/src/legacy/core_plugins/kibana/public/discover/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts index a3a99a0ded523..c56e50f3b27ff 100644 --- a/src/legacy/core_plugins/kibana/public/discover/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { createHashHistory, History } from 'history'; +import { History } from 'history'; import { Capabilities, @@ -51,7 +51,7 @@ export interface DiscoverServices { data: DataPublicPluginStart; docLinks: DocLinksStart; DocViewer: DocViewerComponent; - history: History; + history: () => History; theme: ChartsPluginStart['theme']; filterManager: FilterManager; indexPatterns: IndexPatternsContract; @@ -67,7 +67,8 @@ export interface DiscoverServices { } export async function buildServices( core: CoreStart, - plugins: DiscoverStartPlugins + plugins: DiscoverStartPlugins, + getHistory: () => History ): Promise { const services = { savedObjectsClient: core.savedObjects.client, @@ -77,6 +78,7 @@ export async function buildServices( overlays: core.overlays, }; const savedObjectService = createSavedSearchesLoader(services); + return { addBasePath: core.http.basePath.prepend, capabilities: core.application.capabilities, @@ -85,11 +87,11 @@ export async function buildServices( data: plugins.data, docLinks: core.docLinks, DocViewer: plugins.discover.docViews.DocViewer, - history: createHashHistory(), theme: plugins.charts.theme, filterManager: plugins.data.query.filterManager, getSavedSearchById: async (id: string) => savedObjectService.get(id), getSavedSearchUrlById: async (id: string) => savedObjectService.urlFor(id), + history: getHistory, indexPatterns: plugins.data.indexPatterns, inspector: plugins.inspector, // @ts-ignore diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 0a81ca0222b0a..156267bdfa87e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { createHashHistory } from 'history'; import { DiscoverServices } from './build_services'; import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; import { search } from '../../../../../plugins/data/public'; @@ -52,6 +53,11 @@ export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ setTrackedUrl: (url: string) => void; }>('urlTracker'); +/** + * Makes sure discover and context are using one instance of history + */ +export const getHistory = _.once(() => createHashHistory()); + export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search; export { unhashUrl, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js index 5b03b313e4e3e..032ec7af09a30 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js @@ -81,6 +81,7 @@ function ContextAppRouteController($routeParams, $scope, $route) { defaultStepSize: getServices().uiSettings.get('context:defaultSize'), timeFieldName: indexPattern.timeFieldName, storeInSessionStorage: getServices().uiSettings.get('state:storeInSessionStorage'), + history: getServices().history(), }); this.state = { ...appState.getState() }; this.anchorId = $routeParams.id; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts index ed59143b163f6..b46995d44d826 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import { createBrowserHistory, History } from 'history'; +import { History } from 'history'; import { createStateContainer, createKbnUrlStateStorage, @@ -71,9 +71,9 @@ interface GetStateParams { */ storeInSessionStorage?: boolean; /** - * Browser history used for testing + * History instance to use */ - history?: History; + history: History; } interface GetStateReturn { @@ -126,7 +126,7 @@ export function getState({ }: GetStateParams): GetStateReturn { const stateStorage = createKbnUrlStateStorage({ useHash: storeInSessionStorage, - history: history ? history : createBrowserHistory(), + history, }); const globalStateInitial = stateStorage.get(GLOBAL_STATE_URL_KEY) as GlobalState; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 56966d6294c9a..567cfda45cc0d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -57,7 +57,7 @@ const { core, chrome, data, - history, + history: getHistory, indexPatterns, filterManager, share, @@ -116,6 +116,7 @@ app.config($routeProvider => { reloadOnSearch: false, resolve: { savedObjects: function($route, Promise) { + const history = getHistory(); const savedSearchId = $route.current.params.id; return ensureDefaultIndexPattern(core, data, history).then(() => { const { appStateContainer } = getState({ history }); @@ -204,6 +205,8 @@ function discoverController( return isDefaultType($scope.indexPattern) ? $scope.indexPattern.timeFieldName : undefined; }; + const history = getHistory(); + const { appStateContainer, startSync: startStateSync, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx index 9a6bd65813d18..fdae2c0c16c9f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx @@ -31,11 +31,11 @@ import { IndexPatternField } from '../../../../../../../../plugins/data/public'; jest.mock('../../../kibana_services', () => ({ getServices: () => ({ - history: { + history: () => ({ location: { search: '', }, - }, + }), capabilities: { visualize: { show: true, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.test.tsx index 0df14515adc6d..29451c075bcad 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.test.tsx @@ -36,11 +36,11 @@ import { SavedObject } from '../../../../../../../../core/types'; jest.mock('../../../kibana_services', () => ({ getServices: () => ({ - history: { + history: () => ({ location: { search: '', }, - }, + }), capabilities: { visualize: { show: true, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts index d146d212055b7..968ceeeab73a5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts @@ -125,7 +125,7 @@ export function getVisualizeUrl( services: DiscoverServices ) { const aggsTermSize = services.uiSettings.get('discover:aggs:terms:size'); - const urlParams = parse(services.history.location.search) as Record; + const urlParams = parse(services.history().location.search) as Record; if ( (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) && diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index d05e96ccaaf0b..42883abe98171 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -31,7 +31,7 @@ import { registerFeature } from './np_ready/register_feature'; import './kibana_services'; import { EmbeddableStart, EmbeddableSetup } from '../../../../../plugins/embeddable/public'; import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; -import { setAngularModule, setServices, setUrlTracker } from './kibana_services'; +import { getHistory, setAngularModule, setServices, setUrlTracker } from './kibana_services'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; import { ChartsPluginStart } from '../../../../../plugins/charts/public'; import { buildServices } from './build_services'; @@ -98,6 +98,10 @@ export class DiscoverPlugin implements Plugin { stop: stopUrlTracker, setActiveUrl: setTrackedUrl, } = createKbnUrlTracker({ + // we pass getter here instead of plain `history`, + // so history is lazily created (when app is mounted) + // this prevents redundant `#` when not in discover app + getHistory, baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: '#/discover', storageKey: `lastUrl:${core.http.basePath.get()}:discover`, @@ -174,7 +178,7 @@ export class DiscoverPlugin implements Plugin { if (this.servicesInitialized) { return { core, plugins }; } - const services = await buildServices(core, plugins); + const services = await buildServices(core, plugins, getHistory); setServices(services); this.servicesInitialized = true; diff --git a/src/legacy/core_plugins/vis_type_table/index.ts b/src/legacy/core_plugins/vis_type_table/index.ts deleted file mode 100644 index 04ca9da7de32b..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/index.ts +++ /dev/null @@ -1,44 +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 { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const tableVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'table_vis', - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars: server => ({}), - }, - init: (server: Legacy.Server) => ({}), - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default tableVisPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_table/package.json b/src/legacy/core_plugins/vis_type_table/package.json deleted file mode 100644 index 2809b0e047836..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "table_vis", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/_index.scss b/src/legacy/core_plugins/vis_type_table/public/agg_table/_index.scss deleted file mode 100644 index b19d4a887a7f3..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'agg_table'; diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/_index.scss b/src/legacy/core_plugins/vis_type_table/public/paginated_table/_index.scss deleted file mode 100644 index 9473b847d3c2b..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/paginated_table/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './table_cell_filter'; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts deleted file mode 100644 index 43816121bc23b..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts +++ /dev/null @@ -1,98 +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 { i18n } from '@kbn/i18n'; -import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../../../plugins/vis_default_editor/public'; -import { Vis } from '../../../../plugins/visualizations/public'; -import { tableVisResponseHandler } from './table_vis_response_handler'; -// @ts-ignore -import tableVisTemplate from './table_vis.html'; -import { TableOptions } from './components/table_vis_options'; -import { TableVisualizationController } from './vis_controller'; - -export const tableVisTypeDefinition = { - type: 'table', - name: 'table', - title: i18n.translate('visTypeTable.tableVisTitle', { - defaultMessage: 'Data Table', - }), - icon: 'visTable', - description: i18n.translate('visTypeTable.tableVisDescription', { - defaultMessage: 'Display values in a table', - }), - visualization: TableVisualizationController, - visConfig: { - defaults: { - perPage: 10, - showPartialRows: false, - showMetricsAtAllLevels: false, - sort: { - columnIndex: null, - direction: null, - }, - showTotal: false, - totalFunc: 'sum', - percentageCol: '', - }, - template: tableVisTemplate, - }, - editorConfig: { - optionsTemplate: TableOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { - defaultMessage: 'Metric', - }), - aggFilter: ['!geo_centroid', '!geo_bounds'], - aggSettings: { - top_hits: { - allowStrings: true, - }, - }, - min: 1, - defaults: [{ type: 'count', schema: 'metric' }], - }, - { - group: AggGroupNames.Buckets, - name: 'bucket', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { - defaultMessage: 'Split rows', - }), - aggFilter: ['!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.splitTitle', { - defaultMessage: 'Split table', - }), - min: 0, - max: 1, - aggFilter: ['!filter'], - }, - ]), - }, - responseHandler: tableVisResponseHandler, - hierarchicalData: (vis: Vis) => { - return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); - }, -}; diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts deleted file mode 100644 index 5bb730d2f9b10..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts +++ /dev/null @@ -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 angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; -import $ from 'jquery'; - -import { VisParams, ExprVis } from '../../../../plugins/visualizations/public'; -import { npStart } from './legacy_imports'; -import { getAngularModule } from './get_inner_angular'; -import { initTableVisLegacyModule } from './table_vis_legacy_module'; - -const innerAngularName = 'kibana/table_vis'; - -export class TableVisualizationController { - private tableVisModule: IModule | undefined; - private injector: auto.IInjectorService | undefined; - el: JQuery; - vis: ExprVis; - $rootScope: IRootScopeService | null = null; - $scope: (IScope & { [key: string]: any }) | undefined; - $compile: ICompileService | undefined; - - constructor(domeElement: Element, vis: ExprVis) { - this.el = $(domeElement); - this.vis = vis; - } - - getInjector() { - if (!this.injector) { - const mountpoint = document.createElement('div'); - mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); - this.injector = angular.bootstrap(mountpoint, [innerAngularName]); - this.el.append(mountpoint); - } - - return this.injector; - } - - initLocalAngular() { - if (!this.tableVisModule) { - this.tableVisModule = getAngularModule(innerAngularName, npStart.core); - initTableVisLegacyModule(this.tableVisModule); - } - } - - async render(esResponse: object, visParams: VisParams) { - this.initLocalAngular(); - - return new Promise(async (resolve, reject) => { - if (!this.$rootScope) { - const $injector = this.getInjector(); - this.$rootScope = $injector.get('$rootScope'); - this.$compile = $injector.get('$compile'); - } - const updateScope = () => { - if (!this.$scope) { - return; - } - this.$scope.vis = this.vis; - this.$scope.visState = { params: visParams }; - this.$scope.esResponse = esResponse; - - this.$scope.visParams = visParams; - this.$scope.renderComplete = resolve; - this.$scope.renderFailed = reject; - this.$scope.resize = Date.now(); - this.$scope.$apply(); - }; - - if (!this.$scope && this.$compile) { - this.$scope = this.$rootScope.$new(); - this.$scope.uiState = this.vis.getUiState(); - updateScope(); - this.el.find('div').append(this.$compile(this.vis.type!.visConfig.template)(this.$scope)); - this.$scope.$apply(); - } else { - updateScope(); - } - }); - } - - destroy() { - if (this.$rootScope) { - this.$rootScope.$destroy(); - this.$rootScope = null; - } - } -} diff --git a/src/legacy/ui/public/i18n/index.tsx b/src/legacy/ui/public/i18n/index.tsx index 4d0f5d3a5bd56..c918554563fcb 100644 --- a/src/legacy/ui/public/i18n/index.tsx +++ b/src/legacy/ui/public/i18n/index.tsx @@ -44,7 +44,7 @@ export function wrapInI18nContext

(ComponentToWrap: React.ComponentType

) { } uiModules - .get('i18n') + .get('i18n', ['ngSanitize']) .provider('i18n', I18nProvider) .filter('i18n', i18nFilter) .directive('i18nId', i18nDirective); diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index b62b728beca35..05a4141483587 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -293,6 +293,7 @@ import { convertIPRangeToString, intervalOptions, // only used in Discover isDateHistogramBucketAggConfig, + isNumberType, isStringType, isType, parentPipelineType, @@ -392,6 +393,7 @@ export const search = { InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError, isDateHistogramBucketAggConfig, + isNumberType, isStringType, isType, isValidEsInterval, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 427c4f7864554..6383f61864146 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1545,8 +1545,9 @@ export const search: { InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig; + isNumberType: (agg: import("./search").AggConfig) => boolean; isStringType: (agg: import("./search").AggConfig) => boolean; - isType: (type: string) => (agg: import("./search").AggConfig) => boolean; + isType: (...types: string[]) => (agg: import("./search").AggConfig) => boolean; isValidEsInterval: typeof isValidEsInterval; isValidInterval: typeof isValidInterval; parentPipelineType: string; @@ -1874,21 +1875,21 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index 0beeb1c372275..116f8cfad60f6 100644 --- a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -21,20 +21,22 @@ import { isString, isObject } from 'lodash'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './bucket_agg_type'; import { IAggConfig } from '../agg_config'; -export const isType = (type: string) => { +export const isType = (...types: string[]) => { return (agg: IAggConfig): boolean => { const field = agg.params.field; - return field && field.type === type; + return types.some(type => field && field.type === type); }; }; +export const isNumberType = isType('number'); export const isStringType = isType('string'); +export const isStringOrNumberType = isType('string', 'number'); export const migrateIncludeExcludeFormat = { serialize(this: BucketAggParam, value: any, agg: IBucketAggConfig) { if (this.shouldShow && !this.shouldShow(agg)) return; - if (!value || isString(value)) return value; + if (!value || isString(value) || Array.isArray(value)) return value; else return value.pattern; }, write( @@ -44,7 +46,12 @@ export const migrateIncludeExcludeFormat = { ) { const value = aggConfig.getParam(this.name); - if (isObject(value)) { + if (Array.isArray(value) && value.length > 0 && isNumberType(aggConfig)) { + const parsedValue = value.filter((val): val is number => Number.isFinite(val)); + if (parsedValue.length) { + output.params[this.name] = parsedValue; + } + } else if (isObject(value)) { output.params[this.name] = value.pattern; } else if (value && isStringType(aggConfig)) { output.params[this.name] = value; diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 0dc052bd1fdf6..9769efb6da749 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -75,5 +75,65 @@ describe('Terms Agg', () => { expect(params.include).toBe('404'); expect(params.exclude).toBe('400'); }); + + test('accepts string from string field type and writes this value', () => { + const aggConfigs = getAggConfigs({ + include: 'include value', + exclude: 'exclude value', + field: { + name: 'string_field', + type: 'string', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('string_field'); + expect(params.include).toBe('include value'); + expect(params.exclude).toBe('exclude value'); + }); + + test('accepts empty array from number field type and does not write a value', () => { + const aggConfigs = getAggConfigs({ + include: [], + exclude: [], + field: { + name: 'empty_number_field', + type: 'number', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('empty_number_field'); + expect(params.include).toBe(undefined); + expect(params.exclude).toBe(undefined); + }); + + test('filters array with empty strings from number field type and writes only numbers', () => { + const aggConfigs = getAggConfigs({ + include: [1.1, 2, '', 3.33, ''], + exclude: ['', 4, 5.555, '', 6], + field: { + name: 'number_field', + type: 'number', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('number_field'); + expect(params.include).toStrictEqual([1.1, 2, 3.33]); + expect(params.exclude).toStrictEqual([4, 5.555, 6]); + }); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 5baa38af0e8d6..698e0dfb1d340 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -22,7 +22,10 @@ import { i18n } from '@kbn/i18n'; import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterTerms } from './create_filter/terms'; -import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; +import { + isStringOrNumberType, + migrateIncludeExcludeFormat, +} from './migrate_include_exclude_format'; import { IAggConfigs } from '../agg_configs'; import { Adapters } from '../../../../../inspector/public'; @@ -266,7 +269,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe }), type: 'string', advanced: true, - shouldShow: isStringType, + shouldShow: isStringOrNumberType, ...migrateIncludeExcludeFormat, }, { @@ -276,7 +279,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe }), type: 'string', advanced: true, - shouldShow: isStringType, + shouldShow: isStringOrNumberType, ...migrateIncludeExcludeFormat, }, ], diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx new file mode 100644 index 0000000000000..68f1cf2045efb --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx @@ -0,0 +1,73 @@ +/* + * 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 { HttpSetup } from 'kibana/public'; +import React, { createContext, useContext } from 'react'; + +import { useRequest } from '../../../public'; + +import { Error as CustomError } from './section_error'; + +import { Privileges } from '../types'; + +interface Authorization { + isLoading: boolean; + apiError: CustomError | null; + privileges: Privileges; +} + +const initialValue: Authorization = { + isLoading: true, + apiError: null, + privileges: { + hasAllPrivileges: true, + missingPrivileges: {}, + }, +}; + +export const AuthorizationContext = createContext(initialValue); + +export const useAuthorizationContext = () => { + const ctx = useContext(AuthorizationContext); + if (!ctx) { + throw new Error('AuthorizationContext can only be used inside of AuthorizationProvider!'); + } + return ctx; +}; + +interface Props { + privilegesEndpoint: string; + children: React.ReactNode; + httpClient: HttpSetup; +} + +export const AuthorizationProvider = ({ privilegesEndpoint, httpClient, children }: Props) => { + const { isLoading, error, data: privilegesData } = useRequest(httpClient, { + path: privilegesEndpoint, + method: 'get', + }); + + const value = { + isLoading, + privileges: isLoading ? { hasAllPrivileges: true, missingPrivileges: {} } : privilegesData, + apiError: error ? error : null, + } as Authorization; + + return {children}; +}; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts new file mode 100644 index 0000000000000..71be3cc6152ca --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { + AuthorizationProvider, + AuthorizationContext, + useAuthorizationContext, +} from './authorization_provider'; + +export { WithPrivileges } from './with_privileges'; + +export { NotAuthorizedSection } from './not_authorized_section'; + +export { Error, SectionError } from './section_error'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx new file mode 100644 index 0000000000000..c35f674ef9ec4 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx @@ -0,0 +1,30 @@ +/* + * 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 { EuiEmptyPrompt } from '@elastic/eui'; + +interface Props { + title: React.ReactNode; + message: React.ReactNode | string; +} + +export const NotAuthorizedSection = ({ title, message }: Props) => ( + {title}} body={

{message}

} /> +); diff --git a/x-pack/plugins/snapshot_restore/public/application/components/section_error.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx similarity index 54% rename from x-pack/plugins/snapshot_restore/public/application/components/section_error.tsx rename to src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx index bd9e48796779e..3d56309adae97 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/section_error.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. + * 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 { EuiCallOut, EuiSpacer } from '@elastic/eui'; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/with_privileges.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx similarity index 70% rename from x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/with_privileges.tsx rename to src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx index 223a2882c3cab..8f4b2b976d141 100644 --- a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/with_privileges.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx @@ -1,13 +1,25 @@ /* - * 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. + * 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 { useContext } from 'react'; +import { MissingPrivileges } from '../types'; -import { MissingPrivileges } from '../../../../../common/types'; -import { AuthorizationContext } from './authorization_provider'; +import { useAuthorizationContext } from './authorization_provider'; interface Props { /** @@ -29,7 +41,7 @@ const toArray = (value: string | string[]): string[] => Array.isArray(value) ? (value as string[]) : ([value] as string[]); export const WithPrivileges = ({ privileges: requiredPrivileges, children }: Props) => { - const { isLoading, privileges } = useContext(AuthorizationContext); + const { isLoading, privileges } = useAuthorizationContext(); const privilegesToArray: Privilege[] = toArray(requiredPrivileges).map(p => { const [section, privilege] = p.split('.'); diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts new file mode 100644 index 0000000000000..ad89052b3bb54 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { + WithPrivileges, + NotAuthorizedSection, + AuthorizationProvider, + AuthorizationContext, + SectionError, + Error, + useAuthorizationContext, +} from './components'; + +export { Privileges, MissingPrivileges } from './types'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts new file mode 100644 index 0000000000000..cdc2052122688 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts @@ -0,0 +1,27 @@ +/* + * 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 interface MissingPrivileges { + [key: string]: string[] | undefined; +} + +export interface Privileges { + hasAllPrivileges: boolean; + missingPrivileges: MissingPrivileges; +} diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts b/src/plugins/es_ui_shared/public/authorization/index.ts similarity index 92% rename from src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts rename to src/plugins/es_ui_shared/public/authorization/index.ts index 1030e971d6450..3a02c0d2694f3 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts +++ b/src/plugins/es_ui_shared/public/authorization/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { npSetup, npStart } from 'ui/new_platform'; +export * from '../../__packages_do_not_import__/authorization'; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index a0371bf351193..7e5510d7c9c65 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -47,6 +47,18 @@ export { expandLiteralStrings, } from './console_lang'; +export { + AuthorizationContext, + AuthorizationProvider, + NotAuthorizedSection, + WithPrivileges, + Privileges, + MissingPrivileges, + SectionError, + Error, + useAuthorizationContext, +} from './authorization'; + /** dummy plugin, we just want esUiShared to have its own bundle */ export function plugin() { return new (class EsUiSharedPlugin { diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx index 71cd57ef2d72e..295cf27688c80 100644 --- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx +++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx @@ -92,9 +92,9 @@ export const configureAppAngularModule = ( ) => { const core = 'core' in newPlatform ? newPlatform.core : newPlatform; const packageInfo = - 'injectedMetadata' in newPlatform - ? newPlatform.injectedMetadata.getLegacyMetadata() - : newPlatform.env.packageInfo; + 'env' in newPlatform + ? newPlatform.env.packageInfo + : newPlatform.injectedMetadata.getLegacyMetadata(); if ('injectedMetadata' in newPlatform) { forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts index 6e4c505c62ebc..513c70e60048a 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts @@ -31,6 +31,7 @@ import { setStateToKbnUrl, getStateFromKbnUrl, } from './kbn_url_storage'; +import { ScopedHistory } from '../../../../../core/public'; describe('kbn_url_storage', () => { describe('getStateFromUrl & setStateToUrl', () => { @@ -187,23 +188,54 @@ describe('kbn_url_storage', () => { urlControls.update('/', true); }); - const getCurrentUrl = () => window.location.href; + const getCurrentUrl = () => history.createHref(history.location); it('should flush async url updates', async () => { const pr1 = urlControls.updateAsync(() => '/1', false); const pr2 = urlControls.updateAsync(() => '/2', false); const pr3 = urlControls.updateAsync(() => '/3', false); - expect(getCurrentUrl()).toBe('http://localhost/'); - expect(urlControls.flush()).toBe('http://localhost/3'); - expect(getCurrentUrl()).toBe('http://localhost/3'); + expect(getCurrentUrl()).toBe('/'); + expect(urlControls.flush()).toBe('/3'); + expect(getCurrentUrl()).toBe('/3'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + }); + + it('flush() should return undefined, if no url updates happened', () => { + expect(urlControls.flush()).toBeUndefined(); + urlControls.updateAsync(() => '/1', false); + urlControls.updateAsync(() => '/', false); + expect(urlControls.flush()).toBeUndefined(); + }); + }); + + describe('urlControls - scoped history integration', () => { + let history: History; + let urlControls: IKbnUrlControls; + beforeEach(() => { + const parentHistory = createBrowserHistory(); + parentHistory.replace('/app/kibana/'); + history = new ScopedHistory(parentHistory, '/app/kibana/'); + urlControls = createKbnUrlControls(history); + }); + + const getCurrentUrl = () => history.createHref(history.location); + + it('should flush async url updates', async () => { + const pr1 = urlControls.updateAsync(() => '/app/kibana/1', false); + const pr2 = urlControls.updateAsync(() => '/app/kibana/2', false); + const pr3 = urlControls.updateAsync(() => '/app/kibana/3', false); + expect(getCurrentUrl()).toBe('/app/kibana/'); + expect(urlControls.flush()).toBe('/app/kibana/3'); + expect(getCurrentUrl()).toBe('/app/kibana/3'); await Promise.all([pr1, pr2, pr3]); - expect(getCurrentUrl()).toBe('http://localhost/3'); + expect(getCurrentUrl()).toBe('/app/kibana/3'); }); it('flush() should return undefined, if no url updates happened', () => { expect(urlControls.flush()).toBeUndefined(); - urlControls.updateAsync(() => 'http://localhost/1', false); - urlControls.updateAsync(() => 'http://localhost/', false); + urlControls.updateAsync(() => '/app/kibana/1', false); + urlControls.updateAsync(() => '/app/kibana/', false); expect(urlControls.flush()).toBeUndefined(); }); }); diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts index 40a411d425a54..337d122e2854b 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -154,7 +154,7 @@ export const createKbnUrlControls = ( let shouldReplace = true; function updateUrl(newUrl: string, replace = false): string | undefined { - const currentUrl = getCurrentUrl(); + const currentUrl = getCurrentUrl(history); if (newUrl === currentUrl) return undefined; // skip update const historyPath = getRelativeToHistoryPath(newUrl, history); @@ -165,7 +165,7 @@ export const createKbnUrlControls = ( history.push(historyPath); } - return getCurrentUrl(); + return getCurrentUrl(history); } // queue clean up @@ -187,7 +187,10 @@ export const createKbnUrlControls = ( function getPendingUrl() { if (updateQueue.length === 0) return undefined; - const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl()); + const resultUrl = updateQueue.reduce( + (url, nextUpdate) => nextUpdate(url), + getCurrentUrl(history) + ); return resultUrl; } diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts index af8811b1969e6..8adbbfb06e1ed 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts @@ -57,6 +57,7 @@ export function createKbnUrlTracker({ navLinkUpdater$, toastNotifications, history, + getHistory, storage, shouldTrackUrlUpdate = pathname => { const currentAppName = defaultSubUrl.slice(2); // cut hash and slash symbols @@ -103,6 +104,12 @@ export function createKbnUrlTracker({ * History object to use to track url changes. If this isn't provided, a local history instance will be created. */ history?: History; + + /** + * Lazily retrieve history instance + */ + getHistory?: () => History; + /** * Storage object to use to persist currently active url. If this isn't provided, the browser wide session storage instance will be used. */ @@ -158,7 +165,7 @@ export function createKbnUrlTracker({ function onMountApp() { unsubscribe(); - const historyInstance = history || createHashHistory(); + const historyInstance = history || (getHistory && getHistory()) || createHashHistory(); // track current hash when within app unsubscribeURLHistory = historyInstance.listen(location => { if (shouldTrackUrlUpdate(location.pathname)) { diff --git a/src/plugins/kibana_utils/public/state_management/url/parse.ts b/src/plugins/kibana_utils/public/state_management/url/parse.ts index 95041d0662f56..6339002ea5c68 100644 --- a/src/plugins/kibana_utils/public/state_management/url/parse.ts +++ b/src/plugins/kibana_utils/public/state_management/url/parse.ts @@ -18,12 +18,11 @@ */ import { parse as _parseUrl } from 'url'; +import { History } from 'history'; export const parseUrl = (url: string) => _parseUrl(url, true); export const parseUrlHash = (url: string) => { const hash = parseUrl(url).hash; return hash ? parseUrl(hash.slice(1)) : null; }; -export const getCurrentUrl = () => window.location.href; -export const parseCurrentUrl = () => parseUrl(getCurrentUrl()); -export const parseCurrentUrlHash = () => parseUrlHash(getCurrentUrl()); +export const getCurrentUrl = (history: History) => history.createHref(history.location); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts index cc3f1df7c1e00..8a9a4ea71ee9a 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts @@ -21,6 +21,7 @@ import { createKbnUrlStateStorage, IKbnUrlStateStorage } from './create_kbn_url_ import { History, createBrowserHistory } from 'history'; import { takeUntil, toArray } from 'rxjs/operators'; import { Subject } from 'rxjs'; +import { ScopedHistory } from '../../../../../core/public'; describe('KbnUrlStateStorage', () => { describe('useHash: false', () => { @@ -132,4 +133,78 @@ describe('KbnUrlStateStorage', () => { expect(await result).toEqual([{ test: 'test', ok: 1 }, { test: 'test', ok: 2 }, null]); }); }); + + describe('ScopedHistory integration', () => { + let urlStateStorage: IKbnUrlStateStorage; + let history: ScopedHistory; + const getCurrentUrl = () => history.createHref(history.location); + beforeEach(() => { + const parentHistory = createBrowserHistory(); + parentHistory.push('/kibana/app/'); + history = new ScopedHistory(parentHistory, '/kibana/app/'); + urlStateStorage = createKbnUrlStateStorage({ useHash: false, history }); + }); + + it('should persist state to url', async () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + await urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/#?_s=(ok:1,test:test)"`); + expect(urlStateStorage.get(key)).toEqual(state); + }); + + it('should flush state to url', () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`); + expect(urlStateStorage.flush()).toBe(true); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/#?_s=(ok:1,test:test)"`); + expect(urlStateStorage.get(key)).toEqual(state); + + expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update + }); + + it('should cancel url updates', async () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + const pr = urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`); + urlStateStorage.cancel(); + await pr; + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`); + expect(urlStateStorage.get(key)).toEqual(null); + }); + + it('should cancel url updates if synchronously returned to the same state', async () => { + const state1 = { test: 'test', ok: 1 }; + const state2 = { test: 'test', ok: 2 }; + const key = '_s'; + const pr1 = urlStateStorage.set(key, state1); + await pr1; + const historyLength = history.length; + const pr2 = urlStateStorage.set(key, state2); + const pr3 = urlStateStorage.set(key, state1); + await Promise.all([pr2, pr3]); + expect(history.length).toBe(historyLength); + }); + + it('should notify about url changes', async () => { + expect(urlStateStorage.change$).toBeDefined(); + const key = '_s'; + const destroy$ = new Subject(); + const result = urlStateStorage.change$!(key) + .pipe(takeUntil(destroy$), toArray()) + .toPromise(); + + history.push(`/#?${key}=(ok:1,test:test)`); + history.push(`/?query=test#?${key}=(ok:2,test:test)&some=test`); + history.push(`/?query=test#?some=test`); + + destroy$.next(); + destroy$.complete(); + + expect(await result).toEqual([{ test: 'test', ok: 1 }, { test: 'test', ok: 2 }, null]); + }); + }); }); diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts index ceb91837921eb..18569ef285ff3 100644 --- a/src/plugins/management/public/management_service.test.ts +++ b/src/plugins/management/public/management_service.test.ts @@ -29,9 +29,8 @@ test('Provides default sections', () => { () => {}, coreMock.createSetup().getStartServices ); - expect(service.getAllSections().length).toEqual(3); + expect(service.getAllSections().length).toEqual(2); expect(service.getSection('kibana')).not.toBeUndefined(); - expect(service.getSection('logstash')).not.toBeUndefined(); expect(service.getSection('elasticsearch')).not.toBeUndefined(); }); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts index ed31a22992da8..8fc207e32e6ce 100644 --- a/src/plugins/management/public/management_service.ts +++ b/src/plugins/management/public/management_service.ts @@ -80,7 +80,6 @@ export class ManagementService { ); register({ id: 'kibana', title: 'Kibana', order: 30, euiIconType: 'logoKibana' }); - register({ id: 'logstash', title: 'Logstash', order: 30, euiIconType: 'logoLogstash' }); register({ id: 'elasticsearch', title: 'Elasticsearch', diff --git a/src/plugins/vis_default_editor/public/components/agg_params_map.ts b/src/plugins/vis_default_editor/public/components/agg_params_map.ts index 5af3cfc5b0928..9bc3146b9903b 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_map.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_map.ts @@ -58,6 +58,8 @@ const buckets = { size: controls.SizeParamEditor, }, [BUCKET_TYPES.TERMS]: { + include: controls.IncludeExcludeParamEditor, + exclude: controls.IncludeExcludeParamEditor, orderBy: controls.OrderByParamEditor, orderAgg: controls.OrderAggParamEditor, order: wrapWithInlineComp(controls.OrderParamEditor), diff --git a/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts index dac86249ebbb9..5cb594ade8dba 100644 --- a/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts +++ b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts @@ -119,7 +119,7 @@ function getNextModel(list: NumberRowModel[], range: NumberListRange): NumberRow }; } -function getInitModelList(list: Array): NumberRowModel[] { +function getInitModelList(list: Array): NumberRowModel[] { return list.length ? list.map(num => ({ value: (num === undefined ? EMPTY_STRING : num) as NumberRowModel['value'], diff --git a/src/plugins/vis_default_editor/public/components/controls/components/simple_number_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/simple_number_list.tsx new file mode 100644 index 0000000000000..becf8e47ef573 --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/controls/components/simple_number_list.tsx @@ -0,0 +1,140 @@ +/* + * 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, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; +import { isArray } from 'lodash'; +import { EuiButtonEmpty, EuiFlexItem, EuiFormRow, EuiSpacer, htmlIdGenerator } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EMPTY_STRING, getInitModelList, getRange, parse } from './number_list/utils'; +import { NumberRow, NumberRowModel } from './number_list/number_row'; +import { AggParamEditorProps } from '../../agg_param_props'; + +const generateId = htmlIdGenerator(); + +function SimpleNumberList({ + agg, + aggParam, + value, + setValue, + setTouched, +}: AggParamEditorProps>) { + const [numbers, setNumbers] = useState( + getInitModelList(value && isArray(value) ? value : [EMPTY_STRING]) + ); + const numberRange = useMemo(() => getRange('[-Infinity,Infinity]'), []); + + // This useEffect is needed to discard changes, it sets numbers a mapped value if they are different + useEffect(() => { + if ( + isArray(value) && + (value.length !== numbers.length || + !value.every((numberValue, index) => numberValue === numbers[index].value)) + ) { + setNumbers( + value.map(numberValue => ({ + id: generateId(), + value: numberValue, + isInvalid: false, + })) + ); + } + }, [numbers, value]); + + const onUpdate = useCallback( + (numberList: NumberRowModel[]) => { + setNumbers(numberList); + setValue(numberList.map(({ value: numberValue }) => numberValue)); + }, + [setValue] + ); + + const onChangeValue = useCallback( + (numberField: { id: string; value: string }) => { + onUpdate( + numbers.map(number => + number.id === numberField.id + ? { + id: numberField.id, + value: parse(numberField.value), + isInvalid: false, + } + : number + ) + ); + }, + [numbers, onUpdate] + ); + + // Add an item to the end of the list + const onAdd = useCallback(() => { + const newArray = [ + ...numbers, + { + id: generateId(), + value: EMPTY_STRING as '', + isInvalid: false, + }, + ]; + onUpdate(newArray); + }, [numbers, onUpdate]); + + const onDelete = useCallback( + (id: string) => onUpdate(numbers.filter(number => number.id !== id)), + [numbers, onUpdate] + ); + + return ( + + <> + {numbers.map((number, arrayIndex) => ( + + + {numbers.length - 1 !== arrayIndex && } + + ))} + + + + + + + + + ); +} + +export { SimpleNumberList }; diff --git a/src/plugins/vis_default_editor/public/components/controls/include_exclude.tsx b/src/plugins/vis_default_editor/public/components/controls/include_exclude.tsx new file mode 100644 index 0000000000000..f60f6ce7ce249 --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/controls/include_exclude.tsx @@ -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 React, { useEffect } from 'react'; +import { AggParamEditorProps } from '../agg_param_props'; +import { StringParamEditor } from './string'; +import { search } from '../../../../data/public'; +import { SimpleNumberList } from './components/simple_number_list'; +const { isNumberType } = search.aggs; + +export function IncludeExcludeParamEditor(props: AggParamEditorProps>) { + const { agg, value, setValue } = props; + const isAggOfNumberType = isNumberType(agg); + + // This useEffect converts value from string type to number and back when the field type is changed + useEffect(() => { + if (isAggOfNumberType && !Array.isArray(value) && value !== undefined) { + const numberArray = value + .split('|') + .map(item => parseFloat(item)) + .filter(number => Number.isFinite(number)); + setValue(numberArray.length ? numberArray : ['']); + } else if (!isAggOfNumberType && Array.isArray(value) && value !== undefined) { + setValue(value.filter(item => item !== '').join('|')); + } + }, [isAggOfNumberType, setValue, value]); + + return isAggOfNumberType ? ( + } /> + ) : ( + + ); +} diff --git a/src/plugins/vis_default_editor/public/components/controls/index.ts b/src/plugins/vis_default_editor/public/components/controls/index.ts index e8944aa667853..cfb236e5e22e3 100644 --- a/src/plugins/vis_default_editor/public/components/controls/index.ts +++ b/src/plugins/vis_default_editor/public/components/controls/index.ts @@ -24,6 +24,7 @@ export { ExtendedBoundsParamEditor } from './extended_bounds'; export { FieldParamEditor } from './field'; export { FiltersParamEditor } from './filters'; export { HasExtendedBoundsParamEditor } from './has_extended_bounds'; +export { IncludeExcludeParamEditor } from './include_exclude'; export { IpRangesParamEditor } from './ip_ranges'; export { IpRangeTypeParamEditor } from './ip_range_type'; export { IsFilteredByCollarParamEditor } from './is_filtered_by_collar'; diff --git a/src/plugins/vis_type_table/config.ts b/src/plugins/vis_type_table/config.ts new file mode 100644 index 0000000000000..6749bd83de39f --- /dev/null +++ b/src/plugins/vis_type_table/config.ts @@ -0,0 +1,26 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/vis_type_table/kibana.json b/src/plugins/vis_type_table/kibana.json new file mode 100644 index 0000000000000..bb0f6478a4240 --- /dev/null +++ b/src/plugins/vis_type_table/kibana.json @@ -0,0 +1,11 @@ +{ + "id": "visTypeTable", + "version": "kibana", + "server": true, + "ui": true, + "requiredPlugins": [ + "expressions", + "visualizations", + "data" + ] +} diff --git a/src/legacy/core_plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap b/src/plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap rename to src/plugins/vis_type_table/public/__snapshots__/table_vis_fn.test.ts.snap diff --git a/src/legacy/core_plugins/vis_type_table/public/_table_vis.scss b/src/plugins/vis_type_table/public/_table_vis.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/_table_vis.scss rename to src/plugins/vis_type_table/public/_table_vis.scss diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/_agg_table.scss b/src/plugins/vis_type_table/public/agg_table/_agg_table.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/_agg_table.scss rename to src/plugins/vis_type_table/public/agg_table/_agg_table.scss diff --git a/src/plugins/vis_type_table/public/agg_table/_index.scss b/src/plugins/vis_type_table/public/agg_table/_index.scss new file mode 100644 index 0000000000000..340e08a76f1bd --- /dev/null +++ b/src/plugins/vis_type_table/public/agg_table/_index.scss @@ -0,0 +1 @@ +@import './agg_table'; diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.html b/src/plugins/vis_type_table/public/agg_table/agg_table.html similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.html rename to src/plugins/vis_type_table/public/agg_table/agg_table.html diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js b/src/plugins/vis_type_table/public/agg_table/agg_table.js similarity index 98% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js rename to src/plugins/vis_type_table/public/agg_table/agg_table.js index b9e79f96e4fc1..0cd501e2d0344 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js +++ b/src/plugins/vis_type_table/public/agg_table/agg_table.js @@ -238,9 +238,9 @@ export function KbnAggTable(config, RecursionHelper) { } /** - * @param {[]Object} columns - the formatted columns that will be displayed + * @param {Object[]} columns - the formatted columns that will be displayed * @param {String} title - the title of the column to add to - * @param {[]Object} rows - the row data for the columns + * @param {Object[]} rows - the row data for the columns * @param {Number} insertAtIndex - the index to insert the percentage column at * @returns {Object} - cols and rows for the table to render now included percentage column(s) */ diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table_group.html b/src/plugins/vis_type_table/public/agg_table/agg_table_group.html similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table_group.html rename to src/plugins/vis_type_table/public/agg_table/agg_table_group.html diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table_group.js b/src/plugins/vis_type_table/public/agg_table/agg_table_group.js similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table_group.js rename to src/plugins/vis_type_table/public/agg_table/agg_table_group.js diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/plugins/vis_type_table/public/components/table_vis_options.tsx similarity index 96% rename from src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx rename to src/plugins/vis_type_table/public/components/table_vis_options.tsx index 265528f33f9cd..68348d5ef1060 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_options.tsx @@ -24,12 +24,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { search } from '../../../../../plugins/data/public'; -import { - SwitchOption, - SelectOption, - NumberInputOption, -} from '../../../../../plugins/charts/public'; +import { search } from '../../../data/public'; +import { SwitchOption, SelectOption, NumberInputOption } from '../../../charts/public'; import { TableVisParams } from '../types'; import { totalAggregations } from './utils'; diff --git a/src/legacy/core_plugins/vis_type_table/public/components/utils.ts b/src/plugins/vis_type_table/public/components/utils.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/components/utils.ts rename to src/plugins/vis_type_table/public/components/utils.ts diff --git a/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts b/src/plugins/vis_type_table/public/get_inner_angular.ts similarity index 91% rename from src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts rename to src/plugins/vis_type_table/public/get_inner_angular.ts index 6208e358b4184..d69b9bba31b03 100644 --- a/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts +++ b/src/plugins/vis_type_table/public/get_inner_angular.ts @@ -23,7 +23,7 @@ import angular from 'angular'; import 'angular-recursion'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; +import { CoreStart, IUiSettingsClient, PluginInitializerContext } from 'kibana/public'; import { initAngularBootstrap, PaginateDirectiveProvider, @@ -32,15 +32,15 @@ import { watchMultiDecorator, KbnAccessibleClickProvider, configureAppAngularModule, -} from '../../../../plugins/kibana_legacy/public'; +} from '../../kibana_legacy/public'; initAngularBootstrap(); const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; -export function getAngularModule(name: string, core: CoreStart) { +export function getAngularModule(name: string, core: CoreStart, context: PluginInitializerContext) { const uiModule = getInnerAngular(name, core); - configureAppAngularModule(uiModule, core as LegacyCoreStart, true); + configureAppAngularModule(uiModule, { core, env: context.env }, true); return uiModule; } diff --git a/src/legacy/core_plugins/vis_type_table/public/index.scss b/src/plugins/vis_type_table/public/index.scss similarity index 61% rename from src/legacy/core_plugins/vis_type_table/public/index.scss rename to src/plugins/vis_type_table/public/index.scss index 54124ebc42620..0972c85e0dbe0 100644 --- a/src/legacy/core_plugins/vis_type_table/public/index.scss +++ b/src/plugins/vis_type_table/public/index.scss @@ -1,5 +1,3 @@ -@import 'src/legacy/ui/public/styles/styling_constants'; - // Prefix all styles with "tbv" to avoid conflicts. // Examples // tbvChart @@ -7,6 +5,6 @@ // tbvChart__legend--small // tbvChart__legend-isLoading -@import 'agg_table/index'; -@import 'paginated_table/index'; +@import './agg_table/index'; +@import './paginated_table/index'; @import './table_vis'; diff --git a/src/legacy/core_plugins/vis_type_table/public/index.ts b/src/plugins/vis_type_table/public/index.ts similarity index 92% rename from src/legacy/core_plugins/vis_type_table/public/index.ts rename to src/plugins/vis_type_table/public/index.ts index efbaf69659ea2..5621fdb094772 100644 --- a/src/legacy/core_plugins/vis_type_table/public/index.ts +++ b/src/plugins/vis_type_table/public/index.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - -import { PluginInitializerContext } from '../../../../core/public'; +import './index.scss'; +import { PluginInitializerContext } from 'kibana/public'; import { TableVisPlugin as Plugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/vis_type_table/public/paginated_table/_index.scss b/src/plugins/vis_type_table/public/paginated_table/_index.scss new file mode 100644 index 0000000000000..23d56c09b2818 --- /dev/null +++ b/src/plugins/vis_type_table/public/paginated_table/_index.scss @@ -0,0 +1 @@ +@import './_table_cell_filter'; diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss b/src/plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss rename to src/plugins/vis_type_table/public/paginated_table/_table_cell_filter.scss diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.html b/src/plugins/vis_type_table/public/paginated_table/paginated_table.html similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.html rename to src/plugins/vis_type_table/public/paginated_table/paginated_table.html diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.js b/src/plugins/vis_type_table/public/paginated_table/paginated_table.js similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.js rename to src/plugins/vis_type_table/public/paginated_table/paginated_table.js diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts b/src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts similarity index 98% rename from src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts rename to src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts index 7352236f03feb..23e4aee0378dc 100644 --- a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts +++ b/src/plugins/vis_type_table/public/paginated_table/paginated_table.test.ts @@ -25,10 +25,9 @@ import 'angular-mocks'; import { getAngularModule } from '../get_inner_angular'; import { initTableVisLegacyModule } from '../table_vis_legacy_module'; -import { coreMock } from '../../../../../core/public/mocks'; +import { coreMock } from '../../../../core/public/mocks'; -jest.mock('ui/new_platform'); -jest.mock('../../../../../plugins/kibana_legacy/public/angular/angular_config', () => ({ +jest.mock('../../../kibana_legacy/public/angular/angular_config', () => ({ configureAppAngularModule: () => {}, })); @@ -73,7 +72,11 @@ describe('Table Vis - Paginated table', () => { let paginatedTable: any; const initLocalAngular = () => { - const tableVisModule = getAngularModule('kibana/table_vis', coreMock.createStart()); + const tableVisModule = getAngularModule( + 'kibana/table_vis', + coreMock.createStart(), + coreMock.createPluginInitializerContext() + ); initTableVisLegacyModule(tableVisModule); }; diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/rows.js b/src/plugins/vis_type_table/public/paginated_table/rows.js similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/paginated_table/rows.js rename to src/plugins/vis_type_table/public/paginated_table/rows.js diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/table_cell_filter.html b/src/plugins/vis_type_table/public/paginated_table/table_cell_filter.html similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/paginated_table/table_cell_filter.html rename to src/plugins/vis_type_table/public/paginated_table/table_cell_filter.html diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/plugins/vis_type_table/public/plugin.ts similarity index 80% rename from src/legacy/core_plugins/vis_type_table/public/plugin.ts rename to src/plugins/vis_type_table/public/plugin.ts index ea12a5320a14d..a41d939523bcc 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/plugins/vis_type_table/public/plugin.ts @@ -16,14 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; - -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createTableVisFn } from './table_vis_fn'; -import { tableVisTypeDefinition } from './table_vis_type'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { getTableVisTypeDefinition } from './table_vis_type'; +import { DataPublicPluginStart } from '../../data/public'; import { setFormatService } from './services'; /** @internal */ @@ -40,6 +39,7 @@ export interface TablePluginStartDependencies { /** @internal */ export class TableVisPlugin implements Plugin, void> { initializerContext: PluginInitializerContext; + createBaseVisualization: any; constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; @@ -50,8 +50,9 @@ export class TableVisPlugin implements Plugin, void> { { expressions, visualizations }: TablePluginSetupDependencies ) { expressions.registerFunction(createTableVisFn); - - visualizations.createBaseVisualization(tableVisTypeDefinition); + visualizations.createBaseVisualization( + getTableVisTypeDefinition(core, this.initializerContext) + ); } public start(core: CoreStart, { data }: TablePluginStartDependencies) { diff --git a/src/legacy/core_plugins/vis_type_table/public/services.ts b/src/plugins/vis_type_table/public/services.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_table/public/services.ts rename to src/plugins/vis_type_table/public/services.ts index b4b491ac7a555..3aaffe75e27f1 100644 --- a/src/legacy/core_plugins/vis_type_table/public/services.ts +++ b/src/plugins/vis_type_table/public/services.ts @@ -17,8 +17,8 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { createGetterSetter } from '../../kibana_utils/public'; +import { DataPublicPluginStart } from '../../data/public'; export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis.html b/src/plugins/vis_type_table/public/table_vis.html similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/table_vis.html rename to src/plugins/vis_type_table/public/table_vis.html diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.js b/src/plugins/vis_type_table/public/table_vis_controller.js similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/table_vis_controller.js rename to src/plugins/vis_type_table/public/table_vis_controller.js diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/plugins/vis_type_table/public/table_vis_controller.test.ts similarity index 89% rename from src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts rename to src/plugins/vis_type_table/public/table_vis_controller.test.ts index 8d6f88bf8dd4a..4607324ca150c 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/plugins/vis_type_table/public/table_vis_controller.test.ts @@ -26,24 +26,23 @@ import $ from 'jquery'; import StubIndexPattern from 'test_utils/stub_index_pattern'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; -import { tableVisTypeDefinition } from './table_vis_type'; -import { Vis } from '../../../../plugins/visualizations/public'; +import { getTableVisTypeDefinition } from './table_vis_type'; +import { Vis } from '../../visualizations/public'; // eslint-disable-next-line -import { stubFields } from '../../../../plugins/data/public/stubs'; +import { stubFields } from '../../data/public/stubs'; // eslint-disable-next-line import { tableVisResponseHandler } from './table_vis_response_handler'; -import { coreMock } from '../../../../core/public/mocks'; +import { coreMock } from '../../../core/public/mocks'; +import { IAggConfig, search } from '../../data/public'; +// TODO: remove linting disable // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { npStart } from './legacy_imports'; -import { IAggConfig, search } from '../../../../plugins/data/public'; +import { searchStartMock } from '../../data/public/search/mocks'; -// should be mocked once get rid of 'ui/new_platform' legacy imports -const { createAggConfigs } = npStart.plugins.data.search.aggs; +const { createAggConfigs } = searchStartMock.aggs; const { tabifyAggResponse } = search; -jest.mock('ui/new_platform'); -jest.mock('../../../../plugins/kibana_legacy/public/angular/angular_config', () => ({ +jest.mock('../../kibana_legacy/public/angular/angular_config', () => ({ configureAppAngularModule: () => {}, })); @@ -89,7 +88,11 @@ describe('Table Vis - Controller', () => { let stubIndexPattern: any; const initLocalAngular = () => { - const tableVisModule = getAngularModule('kibana/table_vis', coreMock.createStart()); + const tableVisModule = getAngularModule( + 'kibana/table_vis', + coreMock.createStart(), + coreMock.createPluginInitializerContext() + ); initTableVisLegacyModule(tableVisModule); }; @@ -110,9 +113,13 @@ describe('Table Vis - Controller', () => { (cfg: any) => cfg, 'time', stubFields, - coreMock.createStart() + coreMock.createSetup() ); }); + const tableVisTypeDefinition = getTableVisTypeDefinition( + coreMock.createSetup(), + coreMock.createPluginInitializerContext() + ); function getRangeVis(params?: object) { return ({ diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts b/src/plugins/vis_type_table/public/table_vis_fn.test.ts similarity index 95% rename from src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts rename to src/plugins/vis_type_table/public/table_vis_fn.test.ts index 36392c10f93f3..9accf8950d910 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.test.ts @@ -21,7 +21,7 @@ import { createTableVisFn } from './table_vis_fn'; import { tableVisResponseHandler } from './table_vis_response_handler'; // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; +import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; jest.mock('./table_vis_response_handler', () => ({ tableVisResponseHandler: jest.fn().mockReturnValue({ diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts b/src/plugins/vis_type_table/public/table_vis_fn.ts similarity index 94% rename from src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts rename to src/plugins/vis_type_table/public/table_vis_fn.ts index a97e596e89754..9739a7a284e6c 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.ts @@ -19,11 +19,7 @@ import { i18n } from '@kbn/i18n'; import { tableVisResponseHandler, TableContext } from './table_vis_response_handler'; -import { - ExpressionFunctionDefinition, - KibanaDatatable, - Render, -} from '../../../../plugins/expressions/public'; +import { ExpressionFunctionDefinition, KibanaDatatable, Render } from '../../expressions/public'; export type Input = KibanaDatatable; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts b/src/plugins/vis_type_table/public/table_vis_legacy_module.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts rename to src/plugins/vis_type_table/public/table_vis_legacy_module.ts diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_response_handler.ts b/src/plugins/vis_type_table/public/table_vis_response_handler.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_table/public/table_vis_response_handler.ts rename to src/plugins/vis_type_table/public/table_vis_response_handler.ts diff --git a/src/plugins/vis_type_table/public/table_vis_type.ts b/src/plugins/vis_type_table/public/table_vis_type.ts new file mode 100644 index 0000000000000..26e5ac8cfd71a --- /dev/null +++ b/src/plugins/vis_type_table/public/table_vis_type.ts @@ -0,0 +1,100 @@ +/* + * 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 { CoreSetup, PluginInitializerContext } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { AggGroupNames } from '../../data/public'; +import { Schemas } from '../../vis_default_editor/public'; +import { Vis } from '../../visualizations/public'; +import { tableVisResponseHandler } from './table_vis_response_handler'; +// @ts-ignore +import tableVisTemplate from './table_vis.html'; +import { TableOptions } from './components/table_vis_options'; +import { getTableVisualizationControllerClass } from './vis_controller'; + +export function getTableVisTypeDefinition(core: CoreSetup, context: PluginInitializerContext) { + return { + type: 'table', + name: 'table', + title: i18n.translate('visTypeTable.tableVisTitle', { + defaultMessage: 'Data Table', + }), + icon: 'visTable', + description: i18n.translate('visTypeTable.tableVisDescription', { + defaultMessage: 'Display values in a table', + }), + visualization: getTableVisualizationControllerClass(core, context), + visConfig: { + defaults: { + perPage: 10, + showPartialRows: false, + showMetricsAtAllLevels: false, + sort: { + columnIndex: null, + direction: null, + }, + showTotal: false, + totalFunc: 'sum', + percentageCol: '', + }, + template: tableVisTemplate, + }, + editorConfig: { + optionsTemplate: TableOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { + defaultMessage: 'Metric', + }), + aggFilter: ['!geo_centroid', '!geo_bounds'], + aggSettings: { + top_hits: { + allowStrings: true, + }, + }, + min: 1, + defaults: [{ type: 'count', schema: 'metric' }], + }, + { + group: AggGroupNames.Buckets, + name: 'bucket', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { + defaultMessage: 'Split rows', + }), + aggFilter: ['!filter'], + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.splitTitle', { + defaultMessage: 'Split table', + }), + min: 0, + max: 1, + aggFilter: ['!filter'], + }, + ]), + }, + responseHandler: tableVisResponseHandler, + hierarchicalData: (vis: Vis) => { + return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); + }, + }; +} diff --git a/src/legacy/core_plugins/vis_type_table/public/types.ts b/src/plugins/vis_type_table/public/types.ts similarity index 94% rename from src/legacy/core_plugins/vis_type_table/public/types.ts rename to src/plugins/vis_type_table/public/types.ts index c6de14b9f050c..39023d1305cb6 100644 --- a/src/legacy/core_plugins/vis_type_table/public/types.ts +++ b/src/plugins/vis_type_table/public/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SchemaConfig } from '../../../../plugins/visualizations/public'; +import { SchemaConfig } from '../../visualizations/public'; export enum AggTypes { SUM = 'sum', diff --git a/src/plugins/vis_type_table/public/vis_controller.ts b/src/plugins/vis_type_table/public/vis_controller.ts new file mode 100644 index 0000000000000..d49dd32c8c89c --- /dev/null +++ b/src/plugins/vis_type_table/public/vis_controller.ts @@ -0,0 +1,109 @@ +/* + * 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 { CoreSetup, PluginInitializerContext } from 'kibana/public'; +import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; +import $ from 'jquery'; + +import { VisParams, ExprVis } from '../../visualizations/public'; +import { getAngularModule } from './get_inner_angular'; +import { initTableVisLegacyModule } from './table_vis_legacy_module'; + +const innerAngularName = 'kibana/table_vis'; + +export function getTableVisualizationControllerClass( + core: CoreSetup, + context: PluginInitializerContext +) { + return class TableVisualizationController { + private tableVisModule: IModule | undefined; + private injector: auto.IInjectorService | undefined; + el: JQuery; + vis: ExprVis; + $rootScope: IRootScopeService | null = null; + $scope: (IScope & { [key: string]: any }) | undefined; + $compile: ICompileService | undefined; + + constructor(domeElement: Element, vis: ExprVis) { + this.el = $(domeElement); + this.vis = vis; + } + + getInjector() { + if (!this.injector) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); + this.injector = angular.bootstrap(mountpoint, [innerAngularName]); + this.el.append(mountpoint); + } + + return this.injector; + } + + async initLocalAngular() { + if (!this.tableVisModule) { + const [coreStart] = await core.getStartServices(); + this.tableVisModule = getAngularModule(innerAngularName, coreStart, context); + initTableVisLegacyModule(this.tableVisModule); + } + } + + async render(esResponse: object, visParams: VisParams) { + await this.initLocalAngular(); + + return new Promise(async (resolve, reject) => { + if (!this.$rootScope) { + const $injector = this.getInjector(); + this.$rootScope = $injector.get('$rootScope'); + this.$compile = $injector.get('$compile'); + } + const updateScope = () => { + if (!this.$scope) { + return; + } + this.$scope.vis = this.vis; + this.$scope.visState = { params: visParams }; + this.$scope.esResponse = esResponse; + + this.$scope.visParams = visParams; + this.$scope.renderComplete = resolve; + this.$scope.renderFailed = reject; + this.$scope.resize = Date.now(); + this.$scope.$apply(); + }; + + if (!this.$scope && this.$compile) { + this.$scope = this.$rootScope.$new(); + this.$scope.uiState = this.vis.getUiState(); + updateScope(); + this.el.find('div').append(this.$compile(this.vis.type!.visConfig.template)(this.$scope)); + this.$scope.$apply(); + } else { + updateScope(); + } + }); + } + + destroy() { + if (this.$rootScope) { + this.$rootScope.$destroy(); + this.$rootScope = null; + } + } + }; +} diff --git a/src/plugins/vis_type_table/server/index.ts b/src/plugins/vis_type_table/server/index.ts new file mode 100644 index 0000000000000..882958a28777d --- /dev/null +++ b/src/plugins/vis_type_table/server/index.ts @@ -0,0 +1,34 @@ +/* + * 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 { PluginConfigDescriptor } from 'kibana/server'; + +import { configSchema, ConfigSchema } from '../config'; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('table_vis.enabled', 'vis_type_table.enabled'), + ], +}; + +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/src/plugins/vis_type_timeseries/server/saved_objects/index.ts b/src/plugins/vis_type_timeseries/server/saved_objects/index.ts new file mode 100644 index 0000000000000..5f7f5767f423d --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/saved_objects/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { tsvbTelemetrySavedObjectType } from './tsvb_telemetry'; diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts b/src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts similarity index 100% rename from src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts rename to src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index 779d9441df2fd..e4b8ca19094e4 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -19,7 +19,7 @@ import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../usage_collection/server'; -import { tsvbTelemetrySavedObjectType } from './saved_object_type'; +import { tsvbTelemetrySavedObjectType } from '../saved_objects'; export interface ValidationTelemetryServiceSetup { logFailedValidation: () => void; diff --git a/test/examples/config.js b/test/examples/config.js index 49d75da286075..2be34459d8d06 100644 --- a/test/examples/config.js +++ b/test/examples/config.js @@ -28,6 +28,7 @@ export default async function({ readConfigFile }) { require.resolve('./search'), require.resolve('./embeddables'), require.resolve('./ui_actions'), + require.resolve('./state_sync'), ], services: { ...functionalConfig.get('services'), diff --git a/test/examples/state_sync/index.ts b/test/examples/state_sync/index.ts new file mode 100644 index 0000000000000..3c524f0feb619 --- /dev/null +++ b/test/examples/state_sync/index.ts @@ -0,0 +1,39 @@ +/* + * 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 { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function({ + getService, + getPageObjects, + loadTestFile, +}: PluginFunctionalProviderContext) { + const browser = getService('browser'); + const PageObjects = getPageObjects(['common']); + + describe('state sync examples', function() { + before(async () => { + await browser.setWindowSize(1300, 900); + await PageObjects.common.navigateToApp('settings'); + }); + + loadTestFile(require.resolve('./todo_app')); + }); +} diff --git a/test/examples/state_sync/todo_app.ts b/test/examples/state_sync/todo_app.ts new file mode 100644 index 0000000000000..4933d746ca4fd --- /dev/null +++ b/test/examples/state_sync/todo_app.ts @@ -0,0 +1,189 @@ +/* + * 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 expect from '@kbn/expect'; + +import { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const retry = getService('retry'); + const appsMenu = getService('appsMenu'); + const browser = getService('browser'); + const PageObjects = getPageObjects(['common']); + const log = getService('log'); + + describe('TODO app', () => { + describe("TODO app with browser history (platform's ScopedHistory)", async () => { + const appId = 'stateContainersExampleBrowserHistory'; + let base: string; + + before(async () => { + base = await PageObjects.common.getHostPort(); + await appsMenu.clickLink('State containers example - browser history routing'); + }); + + it('links are rendered correctly and state is preserved in links', async () => { + const getHrefByLinkTestSubj = async (linkTestSubj: string) => + (await testSubjects.find(linkTestSubj)).getAttribute('href'); + + await expectPathname(await getHrefByLinkTestSubj('filterLinkCompleted'), '/completed'); + await expectPathname( + await getHrefByLinkTestSubj('filterLinkNotCompleted'), + '/not-completed' + ); + await expectPathname(await getHrefByLinkTestSubj('filterLinkAll'), '/'); + }); + + it('TODO app state is synced with url, back navigation works', async () => { + // checking that in initial state checkbox is unchecked and state is synced with url + expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(false); + expect(await browser.getCurrentUrl()).to.contain('completed:!f'); + + // check the checkbox by clicking the label (clicking checkbox directly fails as it is "no intractable") + (await find.byCssSelector('label[for="0"]')).click(); + + // wait for react to update dom and checkbox in checked state + await retry.tryForTime(1000, async () => { + await expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(true); + }); + // checking that url is updated with checked state + expect(await browser.getCurrentUrl()).to.contain('completed:!t'); + + // checking back and forward button + await browser.goBack(); + expect(await browser.getCurrentUrl()).to.contain('completed:!f'); + await retry.tryForTime(1000, async () => { + await expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(false); + }); + + await browser.goForward(); + expect(await browser.getCurrentUrl()).to.contain('completed:!t'); + await retry.tryForTime(1000, async () => { + await expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(true); + }); + }); + + it('links navigation works', async () => { + // click link to filter only not completed + await testSubjects.click('filterLinkNotCompleted'); + await expectPathname(await browser.getCurrentUrl(), '/not-completed'); + // checkbox should be missing because it is "completed" + await testSubjects.missingOrFail('todoCheckbox-0'); + }); + + /** + * Parses app's scoped pathname from absolute url and asserts it against `expectedPathname` + * Also checks that hashes are equal (detail of todo app that state is rendered in links) + * @param absoluteUrl + * @param expectedPathname + */ + async function expectPathname(absoluteUrl: string, expectedPathname: string) { + const scoped = await getScopedUrl(absoluteUrl); + const [pathname, newHash] = scoped.split('#'); + expect(pathname).to.be(expectedPathname); + const [, currentHash] = (await browser.getCurrentUrl()).split('#'); + expect(newHash.replace(/%27/g, "'")).to.be(currentHash.replace(/%27/g, "'")); + } + + /** + * Get's part of url scoped to this app (removed kibana's host and app's pathname) + * @param url - absolute url + */ + async function getScopedUrl(url: string): Promise { + expect(url).to.contain(base); + expect(url).to.contain(appId); + const scopedUrl = url.slice(url.indexOf(appId) + appId.length); + expect(scopedUrl).not.to.contain(appId); // app id in url only once + return scopedUrl; + } + }); + + describe('TODO app with hash history ', async () => { + before(async () => { + await appsMenu.clickLink('State containers example - hash history routing'); + }); + + it('Links are rendered correctly and state is preserved in links', async () => { + const getHrefByLinkTestSubj = async (linkTestSubj: string) => + (await testSubjects.find(linkTestSubj)).getAttribute('href'); + await expectHashPathname(await getHrefByLinkTestSubj('filterLinkCompleted'), '/completed'); + await expectHashPathname( + await getHrefByLinkTestSubj('filterLinkNotCompleted'), + '/not-completed' + ); + await expectHashPathname(await getHrefByLinkTestSubj('filterLinkAll'), '/'); + }); + + it('TODO app state is synced with url, back navigation works', async () => { + // checking that in initial state checkbox is unchecked and state is synced with url + expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(false); + expect(await browser.getCurrentUrl()).to.contain('completed:!f'); + // check the checkbox by clicking the label (clicking checkbox directly fails as it is "no intractable") + (await find.byCssSelector('label[for="0"]')).click(); + + // wait for react to update dom and checkbox in checked state + await retry.tryForTime(1000, async () => { + await expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(true); + }); + // checking that url is updated with checked state + expect(await browser.getCurrentUrl()).to.contain('completed:!t'); + + // checking back and forward button + await browser.goBack(); + expect(await browser.getCurrentUrl()).to.contain('completed:!f'); + await retry.tryForTime(1000, async () => { + await expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(false); + }); + + await browser.goForward(); + expect(await browser.getCurrentUrl()).to.contain('completed:!t'); + await retry.tryForTime(1000, async () => { + await expect(await testSubjects.isChecked('todoCheckbox-0')).to.be(true); + }); + }); + + it('links navigation works', async () => { + // click link to filter only not completed + await testSubjects.click('filterLinkNotCompleted'); + await expectHashPathname(await browser.getCurrentUrl(), '/not-completed'); + // checkbox should be missing because it is "completed" + await testSubjects.missingOrFail('todoCheckbox-0'); + }); + + /** + * Parses app's pathname in hash from absolute url and asserts it against `expectedPathname` + * Also checks that queries in hashes are equal (detail of todo app that state is rendered in links) + * @param absoluteUrl + * @param expectedPathname + */ + async function expectHashPathname(hash: string, expectedPathname: string) { + log.debug(`expect hash pathname ${hash} to be ${expectedPathname}`); + const hashPath = hash.split('#')[1]; + const [hashPathname, hashQuery] = hashPath.split('?'); + const [, currentHash] = (await browser.getCurrentUrl()).split('#'); + const [, currentHashQuery] = currentHash.split('?'); + expect(currentHashQuery.replace(/%27/g, "'")).to.be(hashQuery.replace(/%27/g, "'")); + expect(hashPathname).to.be(expectedPathname); + } + }); + }); +} diff --git a/x-pack/index.js b/x-pack/index.js index 7fbd992120ea6..1a78c24b1221b 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -10,7 +10,6 @@ import { monitoring } from './legacy/plugins/monitoring'; import { reporting } from './legacy/plugins/reporting'; import { security } from './legacy/plugins/security'; import { dashboardMode } from './legacy/plugins/dashboard_mode'; -import { logstash } from './legacy/plugins/logstash'; import { beats } from './legacy/plugins/beats_management'; import { apm } from './legacy/plugins/apm'; import { maps } from './legacy/plugins/maps'; @@ -40,7 +39,6 @@ module.exports = function(kibana) { spaces(kibana), security(kibana), dashboardMode(kibana), - logstash(kibana), beats(kibana), apm(kibana), maps(kibana), diff --git a/x-pack/legacy/plugins/logstash/README.md b/x-pack/legacy/plugins/logstash/README.md deleted file mode 100755 index 7d181249300fa..0000000000000 --- a/x-pack/legacy/plugins/logstash/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## Logstash Plugin - -This plugin adds Logstash specific UI code to x-pack. Currently this plugin adds just the management features. diff --git a/x-pack/legacy/plugins/logstash/common/lib/__tests__/get_moment.js b/x-pack/legacy/plugins/logstash/common/lib/__tests__/get_moment.js deleted file mode 100755 index 2e63b231bec32..0000000000000 --- a/x-pack/legacy/plugins/logstash/common/lib/__tests__/get_moment.js +++ /dev/null @@ -1,33 +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 expect from '@kbn/expect'; -import { getMoment } from '../get_moment'; - -describe('get_moment', () => { - describe('getMoment', () => { - it(`returns a moment object when passed a date`, () => { - const moment = getMoment('2017-03-30T14:53:08.121Z'); - - expect(moment.constructor.name).to.be('Moment'); - }); - - it(`returns null when passed falsy`, () => { - const results = [ - getMoment(false), - getMoment(0), - getMoment(''), - getMoment(null), - getMoment(undefined), - getMoment(NaN), - ]; - - results.forEach(result => { - expect(result).to.be(null); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/common/lib/get_moment.js b/x-pack/legacy/plugins/logstash/common/lib/get_moment.js deleted file mode 100755 index 7a808ec9a0336..0000000000000 --- a/x-pack/legacy/plugins/logstash/common/lib/get_moment.js +++ /dev/null @@ -1,15 +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 moment from 'moment'; - -export function getMoment(date) { - if (!date) { - return null; - } - - return moment(date); -} diff --git a/x-pack/legacy/plugins/logstash/index.js b/x-pack/legacy/plugins/logstash/index.js deleted file mode 100755 index 29f01032f3413..0000000000000 --- a/x-pack/legacy/plugins/logstash/index.js +++ /dev/null @@ -1,32 +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 { resolve } from 'path'; -import { registerLicenseChecker } from './server/lib/register_license_checker'; -import { PLUGIN } from '../../../plugins/logstash/common/constants'; - -export const logstash = kibana => - new kibana.Plugin({ - id: PLUGIN.ID, - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - configPrefix: 'xpack.logstash', - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - uiExports: { - managementSections: [ - 'plugins/logstash/sections/pipeline_list', - 'plugins/logstash/sections/pipeline_edit', - ], - home: ['plugins/logstash/lib/register_home_feature'], - }, - init: server => { - registerLicenseChecker(server); - }, - }); diff --git a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts deleted file mode 100644 index 2e1ee2afb9ce6..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts +++ /dev/null @@ -1,35 +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 { i18n } from '@kbn/i18n'; -import { npSetup } from 'ui/new_platform'; -// @ts-ignore -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { FeatureCatalogueCategory } from '../../../../../../src/plugins/home/public'; -// @ts-ignore -import { PLUGIN } from '../../../../../plugins/logstash/common/constants'; - -const { - plugins: { home }, -} = npSetup; - -const enableLinks = Boolean(xpackInfo.get(`features.${PLUGIN.ID}.enableLinks`)); - -if (enableLinks) { - home.featureCatalogue.register({ - id: 'management_logstash', - title: i18n.translate('xpack.logstash.homeFeature.logstashPipelinesTitle', { - defaultMessage: 'Logstash Pipelines', - }), - description: i18n.translate('xpack.logstash.homeFeature.logstashPipelinesDescription', { - defaultMessage: 'Create, delete, update, and clone data ingestion pipelines.', - }), - icon: 'pipelineApp', - path: '/app/kibana#/management/logstash/pipelines', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }); -} diff --git a/x-pack/legacy/plugins/logstash/public/lib/update_management_sections/update_logstash_sections.js b/x-pack/legacy/plugins/logstash/public/lib/update_management_sections/update_logstash_sections.js deleted file mode 100755 index 8da687529c846..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/lib/update_management_sections/update_logstash_sections.js +++ /dev/null @@ -1,22 +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 { management } from 'ui/management'; - -export function updateLogstashSections(pipelineId) { - const editSection = management.getSection('logstash/pipelines/pipeline/edit'); - const newSection = management.getSection('logstash/pipelines/pipeline/new'); - - newSection.hide(); - editSection.hide(); - - if (pipelineId) { - editSection.url = `#/management/logstash/pipelines/pipeline/${pipelineId}/edit`; - editSection.show(); - } else { - newSection.show(); - } -} diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js deleted file mode 100755 index 83446278fdeca..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js +++ /dev/null @@ -1,60 +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 React from 'react'; -import { render } from 'react-dom'; -import { isEmpty } from 'lodash'; -import { uiModules } from 'ui/modules'; -import { npSetup } from 'ui/new_platform'; -import { toastNotifications } from 'ui/notify'; -import { I18nContext } from 'ui/i18n'; -import { PipelineEditor } from '../../../../components/pipeline_editor'; -import 'plugins/logstash/services/license'; -import { logstashSecurity } from 'plugins/logstash/services/security'; -import 'ace'; - -const app = uiModules.get('xpack/logstash'); - -app.directive('pipelineEdit', function($injector) { - const pipelineService = $injector.get('pipelineService'); - const licenseService = $injector.get('logstashLicenseService'); - const kbnUrl = $injector.get('kbnUrl'); - const $route = $injector.get('$route'); - - return { - restrict: 'E', - link: async (scope, el) => { - const close = () => scope.$evalAsync(kbnUrl.change('/management/logstash/pipelines', {})); - const open = id => - scope.$evalAsync(kbnUrl.change(`/management/logstash/pipelines/${id}/edit`)); - - const userResource = logstashSecurity.isSecurityEnabled() - ? await npSetup.plugins.security.authc.getCurrentUser() - : null; - - render( - - - , - el[0] - ); - }, - scope: { - pipeline: '=', - }, - }; -}); diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.js deleted file mode 100755 index 2ef99d3b47672..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.js +++ /dev/null @@ -1,49 +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 React from 'react'; -import { render } from 'react-dom'; -import { isEmpty } from 'lodash'; -import { uiModules } from 'ui/modules'; -import { I18nContext } from 'ui/i18n'; -import { UpgradeFailure } from '../../../../components/upgrade_failure'; - -const app = uiModules.get('xpack/logstash'); - -app.directive('upgradeFailure', $injector => { - const $route = $injector.get('$route'); - const kbnUrl = $injector.get('kbnUrl'); - - return { - link: (scope, el) => { - const onRetry = () => { - $route.updateParams({ retry: true }); - $route.reload(); - }; - const onClose = () => { - scope.$evalAsync(kbnUrl.change('management/logstash/pipelines', {})); - }; - const isNewPipeline = isEmpty(scope.pipeline.id); - const isManualUpgrade = !!$route.current.params.retry; - - render( - - - , - el[0] - ); - }, - restrict: 'E', - scope: { - pipeline: '=', - }, - }; -}); diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.html b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.html deleted file mode 100755 index e1c422d46dfdb..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.js deleted file mode 100755 index 733f7dc3ae2e6..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.js +++ /dev/null @@ -1,87 +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 routes from 'ui/routes'; -import { toastNotifications } from 'ui/notify'; -import { i18n } from '@kbn/i18n'; -import template from './pipeline_edit_route.html'; -import 'plugins/logstash/services/pipeline'; -import 'plugins/logstash/services/license'; -import 'plugins/logstash/services/upgrade'; -import './components/pipeline_edit'; -import './components/upgrade_failure'; -import { updateLogstashSections } from 'plugins/logstash/lib/update_management_sections'; -import { Pipeline } from 'plugins/logstash/models/pipeline'; -import { getPipelineCreateBreadcrumbs, getPipelineEditBreadcrumbs } from '../breadcrumbs'; - -routes - .when('/management/logstash/pipelines/pipeline/:id/edit', { - k7Breadcrumbs: getPipelineEditBreadcrumbs, - }) - .when('/management/logstash/pipelines/new-pipeline', { - k7Breadcrumbs: getPipelineCreateBreadcrumbs, - }) - .defaults(/management\/logstash\/pipelines\/(new-pipeline|pipeline\/:id\/edit)/, { - template: template, - controller: class PipelineEditRouteController { - constructor($injector) { - const $route = $injector.get('$route'); - this.pipeline = $route.current.locals.pipeline; - this.isUpgraded = $route.current.locals.isUpgraded; - } - }, - controllerAs: 'pipelineEditRoute', - resolve: { - logstashTabs: $injector => { - const $route = $injector.get('$route'); - const pipelineId = $route.current.params.id; - updateLogstashSections(pipelineId); - }, - pipeline: function($injector) { - const $route = $injector.get('$route'); - const pipelineService = $injector.get('pipelineService'); - const licenseService = $injector.get('logstashLicenseService'); - const kbnUrl = $injector.get('kbnUrl'); - - const pipelineId = $route.current.params.id; - - if (!pipelineId) return new Pipeline(); - - return pipelineService - .loadPipeline(pipelineId) - .then(pipeline => (!!$route.current.params.clone ? pipeline.clone : pipeline)) - .catch(err => { - return licenseService.checkValidity().then(() => { - if (err.status !== 403) { - toastNotifications.addDanger( - i18n.translate('xpack.logstash.couldNotLoadPipelineErrorNotification', { - defaultMessage: `Couldn't load pipeline. Error: '{errStatusText}'.`, - values: { - errStatusText: err.statusText, - }, - }) - ); - } - - kbnUrl.redirect('/management/logstash/pipelines'); - return Promise.reject(); - }); - }); - }, - checkLicense: $injector => { - const licenseService = $injector.get('logstashLicenseService'); - return licenseService.checkValidity(); - }, - isUpgraded: $injector => { - const upgradeService = $injector.get('upgradeService'); - return upgradeService.executeUpgrade(); - }, - }, - }); - -routes.when('/management/logstash/pipelines/pipeline/:id', { - redirectTo: '/management/logstash/pipelines/pipeline/:id/edit', -}); diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/index.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/index.js deleted file mode 100755 index 7e8ca0e4c2c57..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/index.js +++ /dev/null @@ -1,7 +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 './pipeline_list'; diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.js deleted file mode 100755 index b856979aed8b6..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.js +++ /dev/null @@ -1,58 +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 React from 'react'; -import { render } from 'react-dom'; -import { uiModules } from 'ui/modules'; -import { toastNotifications } from 'ui/notify'; -import { I18nContext } from 'ui/i18n'; -import { PipelineList } from '../../../../components/pipeline_list'; -import 'plugins/logstash/services/pipelines'; -import 'plugins/logstash/services/license'; -import 'plugins/logstash/services/cluster'; -import 'plugins/logstash/services/monitoring'; - -const app = uiModules.get('xpack/logstash'); - -app.directive('pipelineList', function($injector) { - const pipelinesService = $injector.get('pipelinesService'); - const licenseService = $injector.get('logstashLicenseService'); - const clusterService = $injector.get('xpackLogstashClusterService'); - const monitoringService = $injector.get('xpackLogstashMonitoringService'); - const kbnUrl = $injector.get('kbnUrl'); - - return { - restrict: 'E', - link: (scope, el) => { - const openPipeline = id => - scope.$evalAsync(kbnUrl.change(`management/logstash/pipelines/pipeline/${id}/edit`)); - const createPipeline = () => - scope.$evalAsync(kbnUrl.change('management/logstash/pipelines/new-pipeline')); - const clonePipeline = id => - scope.$evalAsync(kbnUrl.change(`management/logstash/pipelines/pipeline/${id}/edit?clone`)); - render( - - - , - el[0] - ); - }, - scope: {}, - controllerAs: 'pipelineList', - }; -}); diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/index.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/index.js deleted file mode 100755 index f60decd1378d5..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/index.js +++ /dev/null @@ -1,8 +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 './register_management_section'; -import './pipeline_list_route'; diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.html b/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.html deleted file mode 100755 index 55b3fabd70161..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.js deleted file mode 100755 index eb593207572c2..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.js +++ /dev/null @@ -1,33 +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 routes from 'ui/routes'; -import { management } from 'ui/management'; -import template from './pipeline_list_route.html'; -import './components/pipeline_list'; -import 'plugins/logstash/services/license'; -import { getPipelineListBreadcrumbs } from '../breadcrumbs'; - -routes.when('/management/logstash/pipelines/', { - template, - k7Breadcrumbs: getPipelineListBreadcrumbs, -}); - -routes.defaults(/\/management/, { - resolve: { - logstashManagementSection: $injector => { - const licenseService = $injector.get('logstashLicenseService'); - const logstashSection = management.getSection('logstash/pipelines'); - - if (licenseService.enableLinks) { - logstashSection.show(); - logstashSection.enable(); - } else { - logstashSection.hide(); - } - }, - }, -}); diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/register_management_section.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/register_management_section.js deleted file mode 100755 index e285418f5f2ae..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_list/register_management_section.js +++ /dev/null @@ -1,36 +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 { management } from 'ui/management'; -import { i18n } from '@kbn/i18n'; - -management.getSection('logstash').register('pipelines', { - display: i18n.translate('xpack.logstash.managementSection.pipelinesTitle', { - defaultMessage: 'Pipelines', - }), - order: 10, - url: '#/management/logstash/pipelines/', -}); - -management.getSection('logstash/pipelines').register('pipeline', { - visible: false, -}); - -management.getSection('logstash/pipelines/pipeline').register('edit', { - display: i18n.translate('xpack.logstash.managementSection.editPipelineTitle', { - defaultMessage: 'Edit pipeline', - }), - order: 1, - visible: false, -}); - -management.getSection('logstash/pipelines/pipeline').register('new', { - display: i18n.translate('xpack.logstash.managementSection.createPipelineTitle', { - defaultMessage: 'Create pipeline', - }), - order: 1, - visible: false, -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.factory.js b/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.factory.js deleted file mode 100755 index 0fee2804c704d..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.factory.js +++ /dev/null @@ -1,13 +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 { uiModules } from 'ui/modules'; -import { ClusterService } from './cluster_service'; - -uiModules.get('xpack/logstash').factory('xpackLogstashClusterService', $injector => { - const $http = $injector.get('$http'); - return new ClusterService($http); -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/cluster/index.js b/x-pack/legacy/plugins/logstash/public/services/cluster/index.js deleted file mode 100755 index ba52657a27ca8..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/cluster/index.js +++ /dev/null @@ -1,7 +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 './cluster_service.factory'; diff --git a/x-pack/legacy/plugins/logstash/public/services/license/index.js b/x-pack/legacy/plugins/logstash/public/services/license/index.js deleted file mode 100755 index 8be8fb5ccbc64..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/license/index.js +++ /dev/null @@ -1,7 +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 './license_service.factory'; diff --git a/x-pack/legacy/plugins/logstash/public/services/license/license_service.factory.js b/x-pack/legacy/plugins/logstash/public/services/license/license_service.factory.js deleted file mode 100755 index 0e131f9b94008..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/license/license_service.factory.js +++ /dev/null @@ -1,14 +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 { uiModules } from 'ui/modules'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import 'ui/url'; -import { LogstashLicenseService } from './logstash_license_service'; - -uiModules.get('xpack/logstash').factory('logstashLicenseService', ($timeout, kbnUrl) => { - return new LogstashLicenseService(xpackInfo, kbnUrl, $timeout); -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js b/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js deleted file mode 100755 index 69cc8614a6ae2..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js +++ /dev/null @@ -1,62 +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 React from 'react'; -import { toastNotifications } from 'ui/notify'; -import { MarkdownSimple } from '../../../../../../../src/plugins/kibana_react/public'; -import { PLUGIN } from '../../../../../../plugins/logstash/common/constants'; - -export class LogstashLicenseService { - constructor(xpackInfoService, kbnUrlService, $timeout) { - this.xpackInfoService = xpackInfoService; - this.kbnUrlService = kbnUrlService; - this.$timeout = $timeout; - } - - get enableLinks() { - return Boolean(this.xpackInfoService.get(`features.${PLUGIN.ID}.enableLinks`)); - } - - get isAvailable() { - return Boolean(this.xpackInfoService.get(`features.${PLUGIN.ID}.isAvailable`)); - } - - get isReadOnly() { - return Boolean(this.xpackInfoService.get(`features.${PLUGIN.ID}.isReadOnly`)); - } - - get message() { - return this.xpackInfoService.get(`features.${PLUGIN.ID}.message`); - } - - notifyAndRedirect() { - toastNotifications.addDanger({ - title: ( - - {this.xpackInfoService.get(`features.${PLUGIN.ID}.message`)} - - ), - }); - this.kbnUrlService.redirect('/management'); - } - - /** - * Checks if the license is valid or the license can perform downgraded UI tasks. - * Otherwise, notifies and redirects. - */ - checkValidity() { - return new Promise((resolve, reject) => { - this.$timeout(() => { - if (this.isAvailable) { - return resolve(); - } - - this.notifyAndRedirect(); - return reject(); - }, 10); // To allow latest XHR call to update license info - }); - } -} diff --git a/x-pack/legacy/plugins/logstash/public/services/monitoring/index.js b/x-pack/legacy/plugins/logstash/public/services/monitoring/index.js deleted file mode 100755 index 83b2105beb5ef..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/monitoring/index.js +++ /dev/null @@ -1,7 +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 './monitoring_service.factory'; diff --git a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.factory.js b/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.factory.js deleted file mode 100755 index 271c776dd6f69..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.factory.js +++ /dev/null @@ -1,18 +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 { uiModules } from 'ui/modules'; -import { MonitoringService } from './monitoring_service'; -import '../cluster'; - -uiModules.get('xpack/logstash').factory('xpackLogstashMonitoringService', $injector => { - const $http = $injector.get('$http'); - const Promise = $injector.get('Promise'); - const monitoringUiEnabled = - $injector.has('monitoringUiEnabled') && $injector.get('monitoringUiEnabled'); - const clusterService = $injector.get('xpackLogstashClusterService'); - return new MonitoringService($http, Promise, monitoringUiEnabled, clusterService); -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/pipeline/index.js b/x-pack/legacy/plugins/logstash/public/services/pipeline/index.js deleted file mode 100755 index 3b0e28bd555e6..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/pipeline/index.js +++ /dev/null @@ -1,7 +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 './pipeline_service.factory'; diff --git a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.factory.js b/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.factory.js deleted file mode 100755 index cf93915425213..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.factory.js +++ /dev/null @@ -1,14 +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 { uiModules } from 'ui/modules'; -import { PipelineService } from './pipeline_service'; - -uiModules.get('xpack/logstash').factory('pipelineService', $injector => { - const $http = $injector.get('$http'); - const pipelinesService = $injector.get('pipelinesService'); - return new PipelineService($http, pipelinesService); -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js b/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js deleted file mode 100755 index b5d0dbeb852d5..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js +++ /dev/null @@ -1,40 +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 chrome from 'ui/chrome'; -import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; -import { Pipeline } from 'plugins/logstash/models/pipeline'; - -export class PipelineService { - constructor($http, pipelinesService) { - this.$http = $http; - this.pipelinesService = pipelinesService; - this.basePath = chrome.addBasePath(ROUTES.API_ROOT); - } - - loadPipeline(id) { - return this.$http.get(`${this.basePath}/pipeline/${id}`).then(response => { - return Pipeline.fromUpstreamJSON(response.data); - }); - } - - savePipeline(pipelineModel) { - return this.$http - .put(`${this.basePath}/pipeline/${pipelineModel.id}`, pipelineModel.upstreamJSON) - .catch(e => { - throw e.data.message; - }); - } - - deletePipeline(id) { - return this.$http - .delete(`${this.basePath}/pipeline/${id}`) - .then(() => this.pipelinesService.addToRecentlyDeleted(id)) - .catch(e => { - throw e.data.message; - }); - } -} diff --git a/x-pack/legacy/plugins/logstash/public/services/pipelines/index.js b/x-pack/legacy/plugins/logstash/public/services/pipelines/index.js deleted file mode 100755 index e273e12d46c6d..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/pipelines/index.js +++ /dev/null @@ -1,7 +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 './pipelines_service.factory'; diff --git a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.factory.js b/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.factory.js deleted file mode 100755 index 9295949e001eb..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.factory.js +++ /dev/null @@ -1,17 +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 { uiModules } from 'ui/modules'; -import { PipelinesService } from './pipelines_service'; -import '../monitoring'; - -uiModules.get('xpack/logstash').factory('pipelinesService', $injector => { - const $http = $injector.get('$http'); - const $window = $injector.get('$window'); - const Promise = $injector.get('Promise'); - const monitoringService = $injector.get('xpackLogstashMonitoringService'); - return new PipelinesService($http, $window, Promise, monitoringService); -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js b/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js deleted file mode 100755 index d70c8be06fde4..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js +++ /dev/null @@ -1,135 +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 chrome from 'ui/chrome'; -import { ROUTES, MONITORING } from '../../../../../../plugins/logstash/common/constants'; -import { PipelineListItem } from 'plugins/logstash/models/pipeline_list_item'; - -const RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY = 'xpack.logstash.recentlyDeletedPipelines'; - -export class PipelinesService { - constructor($http, $window, Promise, monitoringService) { - this.$http = $http; - this.$window = $window; - this.Promise = Promise; - this.monitoringService = monitoringService; - this.basePath = chrome.addBasePath(ROUTES.API_ROOT); - } - - getPipelineList() { - return this.Promise.all([ - this.getManagementPipelineList(), - this.getMonitoringPipelineList(), - ]).then(([managementPipelines, monitoringPipelines]) => { - const now = Date.now(); - - // Monitoring will report centrally-managed pipelines as well, including recently-deleted centrally-managed ones. - // If there's a recently-deleted pipeline we're keeping track of BUT monitoring doesn't report it, that means - // it's not running in Logstash any more. So we can stop tracking it as a recently-deleted pipeline. - const monitoringPipelineIds = monitoringPipelines.map(pipeline => pipeline.id); - this.getRecentlyDeleted().forEach(recentlyDeletedPipeline => { - // We don't want to stop tracking the recently-deleted pipeline until Monitoring has had some - // time to report on it. Otherwise, if we stop tracking first, *then* Monitoring reports it, we'll - // still end up showing it in the list until Monitoring stops reporting it. - if (now - recentlyDeletedPipeline.deletedOn < MONITORING.ACTIVE_PIPELINE_RANGE_S * 1000) { - return; - } - - // If Monitoring is still reporting the pipeline, don't stop tracking it yet - if (monitoringPipelineIds.includes(recentlyDeletedPipeline.id)) { - return; - } - - this.removeFromRecentlyDeleted(recentlyDeletedPipeline.id); - }); - - // Merge centrally-managed pipelines with pipelines reported by monitoring. Take care to dedupe - // while merging because monitoring will (rightly) report centrally-managed pipelines as well, - // including recently-deleted ones! - const managementPipelineIds = managementPipelines.map(pipeline => pipeline.id); - return managementPipelines.concat( - monitoringPipelines.filter( - monitoringPipeline => - !managementPipelineIds.includes(monitoringPipeline.id) && - !this.isRecentlyDeleted(monitoringPipeline.id) - ) - ); - }); - } - - getManagementPipelineList() { - return this.$http - .get(`${this.basePath}/pipelines`) - .then(response => - response.data.pipelines.map(pipeline => PipelineListItem.fromUpstreamJSON(pipeline)) - ); - } - - getMonitoringPipelineList() { - return this.monitoringService.getPipelineList(); - } - - /** - * Delete a collection of pipelines - * - * @param pipelineIds Array of pipeline IDs - * @return Promise { numSuccesses, numErrors } - */ - deletePipelines(pipelineIds) { - const body = { - pipelineIds, - }; - return this.$http.post(`${this.basePath}/pipelines/delete`, body).then(response => { - this.addToRecentlyDeleted(...pipelineIds); - return response.data.results; - }); - } - - addToRecentlyDeleted(...pipelineIds) { - const recentlyDeletedPipelines = this.getRecentlyDeleted(); - const recentlyDeletedPipelineIds = recentlyDeletedPipelines.map(pipeline => pipeline.id); - pipelineIds.forEach(pipelineId => { - if (!recentlyDeletedPipelineIds.includes(pipelineId)) { - recentlyDeletedPipelines.push({ - id: pipelineId, - deletedOn: Date.now(), - }); - } - }); - this.setRecentlyDeleted(recentlyDeletedPipelines); - } - - removeFromRecentlyDeleted(...pipelineIds) { - const recentlyDeletedPipelinesToKeep = this.getRecentlyDeleted().filter( - recentlyDeletedPipeline => !pipelineIds.includes(recentlyDeletedPipeline.id) - ); - this.setRecentlyDeleted(recentlyDeletedPipelinesToKeep); - } - - isRecentlyDeleted(pipelineId) { - return this.getRecentlyDeleted() - .map(pipeline => pipeline.id) - .includes(pipelineId); - } - - getRecentlyDeleted() { - const recentlyDeletedPipelines = this.$window.localStorage.getItem( - RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY - ); - if (!recentlyDeletedPipelines) { - return []; - } - - return JSON.parse(recentlyDeletedPipelines); - } - - setRecentlyDeleted(recentlyDeletedPipelineIds) { - this.$window.localStorage.setItem( - RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY, - JSON.stringify(recentlyDeletedPipelineIds) - ); - } -} diff --git a/x-pack/legacy/plugins/logstash/public/services/security/index.js b/x-pack/legacy/plugins/logstash/public/services/security/index.js deleted file mode 100755 index c9ff911723156..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/security/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { logstashSecurity } from './logstash_security'; diff --git a/x-pack/legacy/plugins/logstash/public/services/security/logstash_security.js b/x-pack/legacy/plugins/logstash/public/services/security/logstash_security.js deleted file mode 100755 index 0949038c9b6c7..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/security/logstash_security.js +++ /dev/null @@ -1,13 +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 { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; - -export const logstashSecurity = { - isSecurityEnabled() { - return Boolean(xpackInfo.get(`features.security`)); - }, -}; diff --git a/x-pack/legacy/plugins/logstash/public/services/upgrade/index.js b/x-pack/legacy/plugins/logstash/public/services/upgrade/index.js deleted file mode 100755 index 345d0d0ff68c6..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/upgrade/index.js +++ /dev/null @@ -1,7 +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 './upgrade_service.factory'; diff --git a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.factory.js b/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.factory.js deleted file mode 100755 index 925c6ae677bdf..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.factory.js +++ /dev/null @@ -1,13 +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 { uiModules } from 'ui/modules'; -import { UpgradeService } from './upgrade_service'; - -uiModules.get('xpack/logstash').factory('upgradeService', $injector => { - const $http = $injector.get('$http'); - return new UpgradeService($http); -}); diff --git a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js b/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js deleted file mode 100755 index 2019bdc1bf1aa..0000000000000 --- a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js +++ /dev/null @@ -1,24 +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 chrome from 'ui/chrome'; -import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; - -export class UpgradeService { - constructor($http) { - this.$http = $http; - this.basePath = chrome.addBasePath(ROUTES.API_ROOT); - } - - executeUpgrade() { - return this.$http - .post(`${this.basePath}/upgrade`) - .then(response => response.data.is_upgraded) - .catch(e => { - throw e.data.message; - }); - } -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/check_license/__tests__/check_license.js b/x-pack/legacy/plugins/logstash/server/lib/check_license/__tests__/check_license.js deleted file mode 100755 index 5fcce0aaa1219..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/check_license/__tests__/check_license.js +++ /dev/null @@ -1,179 +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 expect from '@kbn/expect'; -import { set } from 'lodash'; -import { checkLicense } from '../check_license'; - -describe('check_license', function() { - let mockLicenseInfo; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is undefined', () => { - beforeEach(() => (mockLicenseInfo = undefined)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is > basic', () => { - beforeEach(() => { - set(mockLicenseInfo, 'license.isOneOf', () => true); - mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled - }); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should not set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it('should set isReadOnly to true', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(true); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => { - set(mockLicenseInfo, 'license.isOneOf', () => false); - mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled - }); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& security is disabled', () => { - beforeEach(() => { - mockLicenseInfo.feature = () => ({ isEnabled: () => false }); // Security feature is disabled - set(mockLicenseInfo, 'license.isOneOf', () => true); - set(mockLicenseInfo, 'license.isActive', () => true); - }); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/check_license/check_license.js b/x-pack/legacy/plugins/logstash/server/lib/check_license/check_license.js deleted file mode 100755 index 31136ae1c72a5..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/check_license/check_license.js +++ /dev/null @@ -1,86 +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 { i18n } from '@kbn/i18n'; - -export function checkLicense(xpackLicenseInfo) { - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable the Logstash pipeline UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message: i18n.translate( - 'xpack.logstash.managementSection.notPossibleToManagePipelinesMessage', - { - defaultMessage: - 'You cannot manage Logstash pipelines because license information is not available at this time.', - } - ), - }; - } - - const VALID_LICENSE_MODES = ['trial', 'standard', 'gold', 'platinum', 'enterprise']; - - const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - const isSecurityEnabled = xpackLicenseInfo.feature('security').isEnabled(); - - // Security is not enabled in ES - if (!isSecurityEnabled) { - const message = i18n.translate('xpack.logstash.managementSection.enableSecurityDescription', { - defaultMessage: - 'Security must be enabled in order to use Logstash pipeline management features.' + - ' Please set xpack.security.enabled: true in your elasticsearch.yml.', - }); - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message, - }; - } - - // License is not valid - if (!isLicenseModeValid) { - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message: i18n.translate('xpack.logstash.managementSection.licenseDoesNotSupportDescription', { - defaultMessage: - 'Your {licenseType} license does not support Logstash pipeline management features. Please upgrade your license.', - values: { licenseType }, - }), - }; - } - - // License is valid but not active, we go into a read-only mode. - if (!isLicenseActive) { - return { - isAvailable: true, - enableLinks: true, - isReadOnly: true, - message: i18n.translate( - 'xpack.logstash.managementSection.pipelineCrudOperationsNotAllowedDescription', - { - defaultMessage: - 'You cannot edit, create, or delete your Logstash pipelines because your {licenseType} license has expired.', - values: { licenseType }, - } - ), - }; - } - - // License is valid and active - return { - isAvailable: true, - enableLinks: true, - isReadOnly: false, - }; -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/check_license/index.js b/x-pack/legacy/plugins/logstash/server/lib/check_license/index.js deleted file mode 100755 index f2c070fd44b6e..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/check_license/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { checkLicense } from './check_license'; diff --git a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/index.js b/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/index.js deleted file mode 100755 index 7b0f97c38d129..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js deleted file mode 100755 index a0d06e77b410d..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,21 +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 { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; -import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../../../../plugins/logstash/common/constants'; - -export function registerLicenseChecker(server) { - const xpackMainPlugin = server.plugins.xpack_main; - const logstashPlugin = server.plugins.logstash; - - mirrorPluginStatus(xpackMainPlugin, logstashPlugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx index 4f5655cc9f221..693a7175ebc3e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx @@ -5,6 +5,9 @@ */ import { KibanaServices } from '../../lib/kibana'; + +import { CASES_URL } from '../../../../../../plugins/case/common/constants'; + import { deleteCases, getActionLicense, @@ -22,6 +25,7 @@ import { pushCase, pushToService, } from './api'; + import { actionLicenses, allCases, @@ -44,7 +48,7 @@ import { caseUserActionsSnake, casesStatusSnake, } from './mock'; -import { CASES_URL } from './constants'; + import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index 12b4c80a2dd89..b745361632419 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -20,6 +20,21 @@ import { ActionTypeExecutorResult, } from '../../../../../../plugins/case/common/api'; +import { + CASE_STATUS_URL, + CASES_URL, + CASE_TAGS_URL, + CASE_REPORTERS_URL, + ACTION_TYPES_URL, + ACTION_URL, +} from '../../../../../../plugins/case/common/constants'; + +import { + getCaseDetailsUrl, + getCaseUserActionUrl, + getCaseCommentsUrl, +} from '../../../../../../plugins/case/common/api/helpers'; + import { KibanaServices } from '../../lib/kibana'; import { @@ -33,8 +48,6 @@ import { CaseUserActions, } from './types'; -import { CASES_URL } from './constants'; - import { convertToCamelCase, convertAllCasesToCamel, @@ -54,7 +67,7 @@ export const getCase = async ( includeComments: boolean = true, signal: AbortSignal ): Promise => { - const response = await KibanaServices.get().http.fetch(`${CASES_URL}/${caseId}`, { + const response = await KibanaServices.get().http.fetch(getCaseDetailsUrl(caseId), { method: 'GET', query: { includeComments, @@ -65,18 +78,15 @@ export const getCase = async ( }; export const getCasesStatus = async (signal: AbortSignal): Promise => { - const response = await KibanaServices.get().http.fetch( - `${CASES_URL}/status`, - { - method: 'GET', - signal, - } - ); + const response = await KibanaServices.get().http.fetch(CASE_STATUS_URL, { + method: 'GET', + signal, + }); return convertToCamelCase(decodeCasesStatusResponse(response)); }; export const getTags = async (signal: AbortSignal): Promise => { - const response = await KibanaServices.get().http.fetch(`${CASES_URL}/tags`, { + const response = await KibanaServices.get().http.fetch(CASE_TAGS_URL, { method: 'GET', signal, }); @@ -84,7 +94,7 @@ export const getTags = async (signal: AbortSignal): Promise => { }; export const getReporters = async (signal: AbortSignal): Promise => { - const response = await KibanaServices.get().http.fetch(`${CASES_URL}/reporters`, { + const response = await KibanaServices.get().http.fetch(CASE_REPORTERS_URL, { method: 'GET', signal, }); @@ -96,7 +106,7 @@ export const getCaseUserActions = async ( signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch( - `${CASES_URL}/${caseId}/user_actions`, + getCaseUserActionUrl(caseId), { method: 'GET', signal, @@ -193,14 +203,11 @@ export const patchComment = async ( version: string, signal: AbortSignal ): Promise => { - const response = await KibanaServices.get().http.fetch( - `${CASES_URL}/${caseId}/comments`, - { - method: 'PATCH', - body: JSON.stringify({ comment: commentUpdate, id: commentId, version }), - signal, - } - ); + const response = await KibanaServices.get().http.fetch(getCaseCommentsUrl(caseId), { + method: 'PATCH', + body: JSON.stringify({ comment: commentUpdate, id: commentId, version }), + signal, + }); return convertToCamelCase(decodeCaseResponse(response)); }; @@ -219,7 +226,7 @@ export const pushCase = async ( signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch( - `${CASES_URL}/${caseId}/_push`, + `${getCaseDetailsUrl(caseId)}/_push`, { method: 'POST', body: JSON.stringify(push), @@ -235,7 +242,7 @@ export const pushToService = async ( signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch( - `/api/action/${connectorId}/_execute`, + `${ACTION_URL}/${connectorId}/_execute`, { method: 'POST', body: JSON.stringify({ params: casePushParams }), @@ -251,7 +258,7 @@ export const pushToService = async ( }; export const getActionLicense = async (signal: AbortSignal): Promise => { - const response = await KibanaServices.get().http.fetch(`/api/action/types`, { + const response = await KibanaServices.get().http.fetch(ACTION_TYPES_URL, { method: 'GET', signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts index c24081c777a96..85e472811c93b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts @@ -13,26 +13,27 @@ import { } from '../../../../../../../plugins/case/common/api'; import { KibanaServices } from '../../../lib/kibana'; -import { CASES_CONFIGURE_URL } from '../constants'; +import { + CASE_CONFIGURE_CONNECTORS_URL, + CASE_CONFIGURE_URL, +} from '../../../../../../../plugins/case/common/constants'; + import { ApiProps } from '../types'; import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; import { CaseConfigure } from './types'; export const fetchConnectors = async ({ signal }: ApiProps): Promise => { - const response = await KibanaServices.get().http.fetch( - `${CASES_CONFIGURE_URL}/connectors/_find`, - { - method: 'GET', - signal, - } - ); + const response = await KibanaServices.get().http.fetch(`${CASE_CONFIGURE_CONNECTORS_URL}/_find`, { + method: 'GET', + signal, + }); return response; }; export const getCaseConfigure = async ({ signal }: ApiProps): Promise => { const response = await KibanaServices.get().http.fetch( - CASES_CONFIGURE_URL, + CASE_CONFIGURE_URL, { method: 'GET', signal, @@ -51,7 +52,7 @@ export const postCaseConfigure = async ( signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch( - CASES_CONFIGURE_URL, + CASE_CONFIGURE_URL, { method: 'POST', body: JSON.stringify(caseConfiguration), @@ -68,7 +69,7 @@ export const patchCaseConfigure = async ( signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch( - CASES_CONFIGURE_URL, + CASE_CONFIGURE_URL, { method: 'PATCH', body: JSON.stringify(caseConfiguration), diff --git a/x-pack/legacy/plugins/siem/public/containers/case/constants.ts b/x-pack/legacy/plugins/siem/public/containers/case/constants.ts index ab8dc98db4f64..d8bb499ed7922 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/constants.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/constants.ts @@ -4,7 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CASES_URL = `/api/cases`; -export const CASES_CONFIGURE_URL = `/api/cases/configure`; export const DEFAULT_TABLE_ACTIVE_PAGE = 1; export const DEFAULT_TABLE_LIMIT = 5; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx index 0a30329baf68d..f74c2bad1019e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx @@ -72,104 +72,108 @@ describe('useRuleStatus', () => { cleanup(); }); - test('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRuleStatus('myOwnRuleID') - ); - await waitForNextUpdate(); - expect(result.current).toEqual([true, null, null]); - }); - }); - - test('fetch rule status', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRuleStatus('myOwnRuleID') - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual([ - false, - { - current_status: { - alert_id: 'alertId', - last_failure_at: null, - last_failure_message: null, - last_success_at: 'mm/dd/yyyyTHH:MM:sssz', - last_success_message: 'it is a success', - status: 'succeeded', - status_date: 'mm/dd/yyyyTHH:MM:sssz', - gap: null, - bulk_create_time_durations: ['2235.01'], - search_after_time_durations: ['616.97'], - last_look_back_date: '2020-03-19T00:32:07.996Z', - }, - failures: [], - }, - result.current[2], - ]); - }); - }); - - test('re-fetch rule status', async () => { - const spyOngetRuleStatusById = jest.spyOn(api, 'getRuleStatusById'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRuleStatus('myOwnRuleID') - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - if (result.current[2]) { - result.current[2]('myOwnRuleID'); - } - await waitForNextUpdate(); - expect(spyOngetRuleStatusById).toHaveBeenCalledTimes(2); - }); - }); - - test('init rules statuses', async () => { - const payload = [testRule]; - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRulesStatuses(payload) - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ loading: false, rulesStatuses: [] }); + describe('useRuleStatus', () => { + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useRuleStatus('myOwnRuleID') + ); + await waitForNextUpdate(); + expect(result.current).toEqual([true, null, null]); + }); }); - }); - test('fetch rules statuses', async () => { - const payload = [testRule]; - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRulesStatuses(payload) - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual({ - loading: false, - rulesStatuses: [ + test('fetch rule status', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useRuleStatus('myOwnRuleID') + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual([ + false, { current_status: { alert_id: 'alertId', - bulk_create_time_durations: ['2235.01'], - gap: null, last_failure_at: null, last_failure_message: null, - last_look_back_date: '2020-03-19T00:32:07.996Z', last_success_at: 'mm/dd/yyyyTHH:MM:sssz', last_success_message: 'it is a success', - search_after_time_durations: ['616.97'], status: 'succeeded', status_date: 'mm/dd/yyyyTHH:MM:sssz', + gap: null, + bulk_create_time_durations: ['2235.01'], + search_after_time_durations: ['616.97'], + last_look_back_date: '2020-03-19T00:32:07.996Z', }, failures: [], - id: '12345678987654321', - activate: true, - name: 'Test rule', }, - ], + result.current[2], + ]); + }); + }); + + test('re-fetch rule status', async () => { + const spyOngetRuleStatusById = jest.spyOn(api, 'getRuleStatusById'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useRuleStatus('myOwnRuleID') + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + if (result.current[2]) { + result.current[2]('myOwnRuleID'); + } + await waitForNextUpdate(); + expect(spyOngetRuleStatusById).toHaveBeenCalledTimes(2); + }); + }); + }); + + describe('useRulesStatuses', () => { + test('init rules statuses', async () => { + const payload = [testRule]; + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useRulesStatuses(payload) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ loading: false, rulesStatuses: [] }); + }); + }); + + test('fetch rules statuses', async () => { + const payload = [testRule]; + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useRulesStatuses(payload) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: false, + rulesStatuses: [ + { + current_status: { + alert_id: 'alertId', + bulk_create_time_durations: ['2235.01'], + gap: null, + last_failure_at: null, + last_failure_message: null, + last_look_back_date: '2020-03-19T00:32:07.996Z', + last_success_at: 'mm/dd/yyyyTHH:MM:sssz', + last_success_message: 'it is a success', + search_after_time_durations: ['616.97'], + status: 'succeeded', + status_date: 'mm/dd/yyyyTHH:MM:sssz', + }, + failures: [], + id: '12345678987654321', + activate: true, + name: 'Test rule', + }, + ], + }); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 8bea504f84206..97c89f91c12bd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -243,7 +243,7 @@ export const getMonitoringColumns = (): RulesStatusesColumns[] => { {value != null && value.length > 0 ? Math.max(...value?.map(item => Number.parseFloat(item))) - : null} + : getEmptyTagValue()} ), truncateText: true, @@ -256,7 +256,7 @@ export const getMonitoringColumns = (): RulesStatusesColumns[] => { {value != null && value.length > 0 ? Math.max(...value?.map(item => Number.parseFloat(item))) - : null} + : getEmptyTagValue()} ), truncateText: true, @@ -267,7 +267,7 @@ export const getMonitoringColumns = (): RulesStatusesColumns[] => { name: i18n.COLUMN_GAP, render: (value: RuleStatus['current_status']['gap']) => ( - {value} + {value ?? getEmptyTagValue()} ), truncateText: true, diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts index 514c9759d7b56..e5521558bc2da 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -142,6 +142,25 @@ describe('validateParams()', () => { - [eventAction.2]: expected value to equal [acknowledge]" `); }); + + test('should validate and throw error when timestamp has spaces', () => { + const randoDate = new Date('1963-09-23T01:23:45Z').toISOString(); + const timestamp = ` ${randoDate}`; + expect(() => { + validateParams(actionType, { + timestamp, + }); + }).toThrowError(`error validating action params: error parsing timestamp "${timestamp}"`); + }); + + test('should validate and throw error when timestamp is invalid', () => { + const timestamp = `1963-09-55 90:23:45`; + expect(() => { + validateParams(actionType, { + timestamp, + }); + }).toThrowError(`error validating action params: error parsing timestamp "${timestamp}"`); + }); }); describe('execute()', () => { diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index 2b607d0dd41ba..f4d69a4a39e40 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -70,18 +70,26 @@ const ParamsSchema = schema.object( function validateParams(paramsObject: any): string | void { const params: ActionParamsType = paramsObject; - const { timestamp } = params; if (timestamp != null) { - let date; try { - date = Date.parse(timestamp); + const date = Date.parse(timestamp); + if (isNaN(date)) { + return i18n.translate('xpack.actions.builtin.pagerduty.invalidTimestampErrorMessage', { + defaultMessage: `error parsing timestamp "{timestamp}"`, + values: { + timestamp, + }, + }); + } } catch (err) { - return 'error parsing timestamp: ${err.message}'; - } - - if (isNaN(date)) { - return 'error parsing timestamp'; + return i18n.translate('xpack.actions.builtin.pagerduty.timestampParsingFailedErrorMessage', { + defaultMessage: `error parsing timestamp "{timestamp}": {message}`, + values: { + timestamp, + message: err.message, + }, + }); } } } diff --git a/x-pack/plugins/case/common/api/helpers.ts b/x-pack/plugins/case/common/api/helpers.ts new file mode 100644 index 0000000000000..0efdcd3819659 --- /dev/null +++ b/x-pack/plugins/case/common/api/helpers.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CASE_DETAILS_URL, + CASE_COMMENTS_URL, + CASE_USER_ACTIONS_URL, + CASE_COMMENT_DETAILS_URL, +} from '../constants'; + +export const getCaseDetailsUrl = (id: string): string => { + return CASE_DETAILS_URL.replace('{case_id}', id); +}; + +export const getCaseCommentsUrl = (id: string): string => { + return CASE_COMMENTS_URL.replace('{case_id}', id); +}; + +export const getCaseCommentDetailsUrl = (caseId: string, commentId: string): string => { + return CASE_COMMENT_DETAILS_URL.replace('{case_id}', caseId).replace('{comment_id}', commentId); +}; + +export const getCaseUserActionUrl = (id: string): string => { + return CASE_USER_ACTIONS_URL.replace('{case_id}', id); +}; diff --git a/x-pack/plugins/case/common/constants.ts b/x-pack/plugins/case/common/constants.ts new file mode 100644 index 0000000000000..dcfa46bfa6019 --- /dev/null +++ b/x-pack/plugins/case/common/constants.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const APP_ID = 'case'; + +/** + * Case routes + */ + +export const CASES_URL = '/api/cases'; +export const CASE_DETAILS_URL = `${CASES_URL}/{case_id}`; +export const CASE_CONFIGURE_URL = `${CASES_URL}/configure`; +export const CASE_CONFIGURE_CONNECTORS_URL = `${CASE_CONFIGURE_URL}/connectors`; +export const CASE_COMMENTS_URL = `${CASE_DETAILS_URL}/comments`; +export const CASE_COMMENT_DETAILS_URL = `${CASE_DETAILS_URL}/comments/{comment_id}`; +export const CASE_REPORTERS_URL = `${CASES_URL}/reporters`; +export const CASE_STATUS_URL = `${CASES_URL}/status`; +export const CASE_TAGS_URL = `${CASES_URL}/tags`; +export const CASE_USER_ACTIONS_URL = `${CASE_DETAILS_URL}/user_actions`; + +/** + * Action routes + */ + +export const ACTION_URL = '/api/action'; +export const ACTION_TYPES_URL = '/api/action/types'; diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts index 1dfab165eccd7..e9bcb9690ebd8 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts @@ -9,11 +9,12 @@ import { schema } from '@kbn/config-schema'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; export function initDeleteAllCommentsApi({ caseService, router, userActionService }: RouteDeps) { router.delete( { - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts index b2022e6dec26d..67cb998409570 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts @@ -15,6 +15,7 @@ import { mockCaseComments, } from '../../__fixtures__'; import { initDeleteCommentApi } from './delete_comment'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; describe('DELETE comment', () => { let routeHandler: RequestHandler; @@ -23,7 +24,7 @@ describe('DELETE comment', () => { }); it(`deletes the comment. responds with 204`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments/{comment_id}', + path: CASE_COMMENT_DETAILS_URL, method: 'delete', params: { case_id: 'mock-id-1', @@ -43,7 +44,7 @@ describe('DELETE comment', () => { }); it(`returns an error when thrown from deleteComment service`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments/{comment_id}', + path: CASE_COMMENT_DETAILS_URL, method: 'delete', params: { case_id: 'mock-id-1', diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts index ff0729afed96a..72ef400415d0f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts @@ -11,11 +11,12 @@ import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; export function initDeleteCommentApi({ caseService, router, userActionService }: RouteDeps) { router.delete( { - path: '/api/cases/{case_id}/comments/{comment_id}', + path: CASE_COMMENT_DETAILS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts b/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts index 92da64cebee74..3df9fdb80ba8a 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts @@ -18,11 +18,12 @@ import { } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { escapeHatch, transformComments, wrapError } from '../../utils'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; export function initFindCaseCommentsApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/{case_id}/comments/_find', + path: `${CASE_COMMENTS_URL}/_find`, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts index 1500039eb2cc2..8d7820d4e8fec 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts @@ -9,11 +9,12 @@ import { schema } from '@kbn/config-schema'; import { AllCommentsResponseRt } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { flattenCommentSavedObjects, wrapError } from '../../utils'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; export function initGetAllCommentsApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts index 9c8d0e5254df0..b5a7d6367ea4b 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts @@ -15,6 +15,7 @@ import { } from '../../__fixtures__'; import { flattenCommentSavedObject } from '../../utils'; import { initGetCommentApi } from './get_comment'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; describe('GET comment', () => { let routeHandler: RequestHandler; @@ -23,7 +24,7 @@ describe('GET comment', () => { }); it(`returns the comment`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments/{comment_id}', + path: CASE_COMMENT_DETAILS_URL, method: 'get', params: { case_id: 'mock-id-1', @@ -48,7 +49,7 @@ describe('GET comment', () => { }); it(`returns an error when getComment throws`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments/{comment_id}', + path: CASE_COMMENT_DETAILS_URL, method: 'get', params: { case_id: 'mock-id-1', diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.ts index 24f44a5f5129b..5fa668f6ae5de 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.ts @@ -9,11 +9,12 @@ import { schema } from '@kbn/config-schema'; import { CommentResponseRt } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { flattenCommentSavedObject, wrapError } from '../../utils'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; export function initGetCommentApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/{case_id}/comments/{comment_id}', + path: CASE_COMMENT_DETAILS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts index 8d9906c2abe7f..04473e302e468 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts @@ -14,6 +14,7 @@ import { mockCases, } from '../../__fixtures__'; import { initPatchCommentApi } from './patch_comment'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; describe('PATCH comment', () => { let routeHandler: RequestHandler; @@ -22,7 +23,7 @@ describe('PATCH comment', () => { }); it(`Patch a comment`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'patch', params: { case_id: 'mock-id-1', @@ -50,7 +51,7 @@ describe('PATCH comment', () => { it(`Fails with 409 if version does not match`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'patch', params: { case_id: 'mock-id-1', @@ -74,7 +75,7 @@ describe('PATCH comment', () => { }); it(`Returns an error if updateComment throws`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'patch', params: { case_id: 'mock-id-1', diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts index 3b38afc02ed81..dd9b124ff1b79 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts @@ -15,11 +15,12 @@ import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; import { escapeHatch, wrapError, flattenCaseSavedObject } from '../../utils'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; export function initPatchCommentApi({ caseService, router, userActionService }: RouteDeps) { router.patch( { - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts index 23039da681ec6..9006470f36f36 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts @@ -15,6 +15,7 @@ import { mockCaseComments, } from '../../__fixtures__'; import { initPostCommentApi } from './post_comment'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; describe('POST comment', () => { let routeHandler: RequestHandler; @@ -27,7 +28,7 @@ describe('POST comment', () => { }); it(`Posts a new comment`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'post', params: { case_id: 'mock-id-1', @@ -52,7 +53,7 @@ describe('POST comment', () => { }); it(`Returns an error if the case does not exist`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'post', params: { case_id: 'this-is-not-real', @@ -75,7 +76,7 @@ describe('POST comment', () => { }); it(`Returns an error if postNewCase throws`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'post', params: { case_id: 'mock-id-1', @@ -100,7 +101,7 @@ describe('POST comment', () => { routeHandler = await createRoute(initPostCommentApi, 'post', true); const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, method: 'post', params: { case_id: 'mock-id-1', diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts index 70405af26f576..a296d9815f251 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts @@ -15,11 +15,12 @@ import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { escapeHatch, transformNewComment, wrapError, flattenCaseSavedObject } from '../../utils'; import { RouteDeps } from '../../types'; +import { CASE_COMMENTS_URL } from '../../../../../common/constants'; export function initPostCommentApi({ caseService, router, userActionService }: RouteDeps) { router.post( { - path: '/api/cases/{case_id}/comments', + path: CASE_COMMENTS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts index 66d39c3f11d28..5b3b6e77b9403 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts @@ -15,6 +15,7 @@ import { import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; import { initGetCaseConfigure } from './get_configure'; +import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; describe('GET configuration', () => { let routeHandler: RequestHandler; @@ -24,7 +25,7 @@ describe('GET configuration', () => { it('returns the configuration', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'get', }); @@ -44,7 +45,7 @@ describe('GET configuration', () => { it('handles undefined version correctly', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'get', }); @@ -78,7 +79,7 @@ describe('GET configuration', () => { it('returns an empty object when there is no configuration', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'get', }); @@ -95,7 +96,7 @@ describe('GET configuration', () => { it('returns an error if find throws an error', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'get', }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts index 2832edaa892d5..03bec1fe72d39 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts @@ -7,11 +7,12 @@ import { CaseConfigureResponseRt } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; export function initGetCaseConfigure({ caseConfigureService, caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, validate: false, }, async (context, request, response) => { diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts index 62edaa0a4792a..09692ff73b94b 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts @@ -16,6 +16,7 @@ import { import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; import { initCaseConfigureGetActionConnector } from './get_connectors'; import { getActions } from '../../__mocks__/request_responses'; +import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../common/constants'; describe('GET connectors', () => { let routeHandler: RequestHandler; @@ -25,7 +26,7 @@ describe('GET connectors', () => { it('returns the connectors', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure/connectors/_find', + path: `${CASE_CONFIGURE_CONNECTORS_URL}/_find`, method: 'get', }); @@ -44,7 +45,7 @@ describe('GET connectors', () => { it('it throws an error when actions client is null', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure/connectors/_find', + path: `${CASE_CONFIGURE_CONNECTORS_URL}/_find`, method: 'get', }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts index 3e9a1c96d55ed..00575655d4c42 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts @@ -8,6 +8,8 @@ import Boom from 'boom'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../common/constants'; + /* * Be aware that this api will only return 20 connectors */ @@ -17,7 +19,7 @@ const CASE_SERVICE_NOW_ACTION = '.servicenow'; export function initCaseConfigureGetActionConnector({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/configure/connectors/_find', + path: `${CASE_CONFIGURE_CONNECTORS_URL}/_find`, validate: false, }, async (context, request, response) => { diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts index 5b3d68a258664..9b71f777b95ab 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts @@ -15,6 +15,7 @@ import { import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; import { initPatchCaseConfigure } from './patch_configure'; +import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; describe('PATCH configuration', () => { let routeHandler: RequestHandler; @@ -29,7 +30,7 @@ describe('PATCH configuration', () => { it('patch configuration', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'patch', body: { closure_type: 'close-by-pushing', @@ -61,7 +62,7 @@ describe('PATCH configuration', () => { routeHandler = await createRoute(initPatchCaseConfigure, 'patch', true); const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'patch', body: { closure_type: 'close-by-pushing', @@ -91,7 +92,7 @@ describe('PATCH configuration', () => { it('throw error when configuration have not being created', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'patch', body: { closure_type: 'close-by-pushing', @@ -113,7 +114,7 @@ describe('PATCH configuration', () => { it('throw error when the versions are different', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'patch', body: { closure_type: 'close-by-pushing', @@ -135,7 +136,7 @@ describe('PATCH configuration', () => { it('handles undefined version correctly', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'patch', body: { connector_id: 'no-version', version: mockCaseConfigure[0].version }, }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts index 3a1b9d5059cbc..47f7d503e32b8 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts @@ -16,11 +16,12 @@ import { } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { wrapError, escapeHatch } from '../../utils'; +import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; export function initPatchCaseConfigure({ caseConfigureService, caseService, router }: RouteDeps) { router.patch( { - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, validate: { body: escapeHatch, }, diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts index 7e40cad5b1298..fb95cc53a1710 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts @@ -16,6 +16,7 @@ import { import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; import { initPostCaseConfigure } from './post_configure'; import { newConfiguration } from '../../__mocks__/request_responses'; +import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; describe('POST configuration', () => { let routeHandler: RequestHandler; @@ -30,7 +31,7 @@ describe('POST configuration', () => { it('create configuration', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -61,7 +62,7 @@ describe('POST configuration', () => { routeHandler = await createRoute(initPostCaseConfigure, 'post', true); const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -90,7 +91,7 @@ describe('POST configuration', () => { it('throws when missing connector_id', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: { connector_name: 'My connector 2', @@ -111,7 +112,7 @@ describe('POST configuration', () => { it('throws when missing connector_name', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: { connector_id: '456', @@ -132,7 +133,7 @@ describe('POST configuration', () => { it('throws when missing closure_type', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: { connector_id: '456', @@ -153,7 +154,7 @@ describe('POST configuration', () => { it('it deletes the previous configuration', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -172,7 +173,7 @@ describe('POST configuration', () => { it('it does NOT delete when not found', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -191,7 +192,7 @@ describe('POST configuration', () => { it('it deletes all configuration', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -214,7 +215,7 @@ describe('POST configuration', () => { it('returns an error if find throws an error', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -232,7 +233,7 @@ describe('POST configuration', () => { it('returns an error if delete throws an error', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: newConfiguration, }); @@ -250,7 +251,7 @@ describe('POST configuration', () => { it('returns an error if post throws an error', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: { connector_id: 'throw-error-create', @@ -272,7 +273,7 @@ describe('POST configuration', () => { it('handles undefined version correctly', async () => { const req = httpServerMock.createKibanaRequest({ - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, method: 'post', body: { ...newConfiguration, connector_id: 'no-version' }, }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts index 2a23abf0cbf21..5c1693e728c37 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts @@ -16,11 +16,12 @@ import { } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { wrapError, escapeHatch } from '../../utils'; +import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; export function initPostCaseConfigure({ caseConfigureService, caseService, router }: RouteDeps) { router.post( { - path: '/api/cases/configure', + path: CASE_CONFIGURE_URL, validate: { body: escapeHatch, }, diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts index c5be6f78a1570..e655339e05eb1 100644 --- a/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts @@ -16,6 +16,7 @@ import { mockCaseComments, } from '../__fixtures__'; import { initDeleteCasesApi } from './delete_cases'; +import { CASES_URL } from '../../../../common/constants'; describe('DELETE case', () => { let routeHandler: RequestHandler; @@ -24,7 +25,7 @@ describe('DELETE case', () => { }); it(`deletes the case. responds with 204`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'delete', query: { ids: ['mock-id-1'], @@ -43,7 +44,7 @@ describe('DELETE case', () => { }); it(`returns an error when thrown from deleteCase service`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'delete', query: { ids: ['not-real'], @@ -62,7 +63,7 @@ describe('DELETE case', () => { }); it(`returns an error when thrown from getAllCaseComments service`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'delete', query: { ids: ['bad-guy'], @@ -81,7 +82,7 @@ describe('DELETE case', () => { }); it(`returns an error when thrown from deleteComment service`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'delete', query: { ids: ['valid-id'], diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts index 0214017ae5c29..20591637a6c23 100644 --- a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts @@ -9,11 +9,12 @@ import { schema } from '@kbn/config-schema'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; +import { CASES_URL } from '../../../../common/constants'; export function initDeleteCasesApi({ caseService, router, userActionService }: RouteDeps) { router.delete( { - path: '/api/cases', + path: CASES_URL, validate: { query: schema.object({ ids: schema.arrayOf(schema.string()), diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts index 8fafb1af0eb82..7af1cee494457 100644 --- a/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts @@ -14,6 +14,7 @@ import { mockCases, } from '../__fixtures__'; import { initFindCasesApi } from './find_cases'; +import { CASES_URL } from '../../../../common/constants'; describe('GET all cases', () => { let routeHandler: RequestHandler; @@ -22,7 +23,7 @@ describe('GET all cases', () => { }); it(`gets all the cases`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: `${CASES_URL}/_find`, method: 'get', }); diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts b/x-pack/plugins/case/server/routes/api/cases/find_cases.ts index b2716749e9749..40fc0301b058a 100644 --- a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/find_cases.ts @@ -15,6 +15,7 @@ import { CasesFindResponseRt, CasesFindRequestRt, throwErrors } from '../../../. import { transformCases, sortToSnake, wrapError, escapeHatch } from '../utils'; import { RouteDeps, TotalCommentByCase } from '../types'; import { CASE_SAVED_OBJECT } from '../../../saved_object_types'; +import { CASES_URL } from '../../../../common/constants'; const combineFilters = (filters: string[], operator: 'OR' | 'AND'): string => filters?.filter(i => i !== '').join(` ${operator} `); @@ -41,7 +42,7 @@ const buildFilter = ( export function initFindCasesApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/_find', + path: `${CASES_URL}/_find`, validate: { query: escapeHatch, }, diff --git a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts index 5912df2c40aa3..a8c12d4734b53 100644 --- a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts @@ -18,6 +18,7 @@ import { } from '../__fixtures__'; import { flattenCaseSavedObject } from '../utils'; import { initGetCaseApi } from './get_case'; +import { CASE_DETAILS_URL } from '../../../../common/constants'; describe('GET case', () => { let routeHandler: RequestHandler; @@ -26,7 +27,7 @@ describe('GET case', () => { }); it(`returns the case with empty case comments when includeComments is false`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}', + path: CASE_DETAILS_URL, method: 'get', params: { case_id: 'mock-id-1', @@ -55,7 +56,7 @@ describe('GET case', () => { }); it(`returns an error when thrown from getCase`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}', + path: CASE_DETAILS_URL, method: 'get', params: { case_id: 'abcdefg', @@ -78,7 +79,7 @@ describe('GET case', () => { }); it(`returns the case with case comments when includeComments is true`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}', + path: CASE_DETAILS_URL, method: 'get', params: { case_id: 'mock-id-1', @@ -102,7 +103,7 @@ describe('GET case', () => { }); it(`returns an error when thrown from getAllCaseComments`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases/{case_id}', + path: CASE_DETAILS_URL, method: 'get', params: { case_id: 'bad-guy', diff --git a/x-pack/plugins/case/server/routes/api/cases/get_case.ts b/x-pack/plugins/case/server/routes/api/cases/get_case.ts index ac32b20541a9c..1e836d38c285c 100644 --- a/x-pack/plugins/case/server/routes/api/cases/get_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/get_case.ts @@ -9,11 +9,12 @@ import { schema } from '@kbn/config-schema'; import { CaseResponseRt } from '../../../../common/api'; import { RouteDeps } from '../types'; import { flattenCaseSavedObject, wrapError } from '../utils'; +import { CASE_DETAILS_URL } from '../../../../common/constants'; export function initGetCaseApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/{case_id}', + path: CASE_DETAILS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts index c36ea8964dc80..57f9fc20dbf34 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts @@ -20,11 +20,12 @@ import { escapeHatch, wrapError, flattenCaseSavedObject } from '../utils'; import { RouteDeps } from '../types'; import { getCaseToUpdate } from './helpers'; import { buildCaseUserActions } from '../../../services/user_actions/helpers'; +import { CASES_URL } from '../../../../common/constants'; export function initPatchCasesApi({ caseService, router, userActionService }: RouteDeps) { router.patch( { - path: '/api/cases', + path: CASES_URL, validate: { body: escapeHatch, }, diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts index 5899102224774..0bbceb5214046 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts @@ -14,6 +14,7 @@ import { mockCases, } from '../__fixtures__'; import { initPostCaseApi } from './post_case'; +import { CASES_URL } from '../../../../common/constants'; describe('POST cases', () => { let routeHandler: RequestHandler; @@ -26,7 +27,7 @@ describe('POST cases', () => { }); it(`Posts a new case`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'post', body: { description: 'This is a brand new case of a bad meanie defacing data', @@ -49,7 +50,7 @@ describe('POST cases', () => { it(`Error if you passing status for a new case`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'post', body: { description: 'This is a brand new case of a bad meanie defacing data', @@ -70,7 +71,7 @@ describe('POST cases', () => { }); it(`Returns an error if postNewCase throws`, async () => { const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'post', body: { description: 'Throw an error', @@ -93,7 +94,7 @@ describe('POST cases', () => { routeHandler = await createRoute(initPostCaseApi, 'post', true); const request = httpServerMock.createKibanaRequest({ - path: '/api/cases', + path: CASES_URL, method: 'post', body: { description: 'This is a brand new case of a bad meanie defacing data', diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.ts index 239b8bfdf9b29..059a8b1affd54 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.ts @@ -14,11 +14,12 @@ import { flattenCaseSavedObject, transformNewCase, wrapError, escapeHatch } from import { CasePostRequestRt, throwErrors, excess, CaseResponseRt } from '../../../../common/api'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; +import { CASES_URL } from '../../../../common/constants'; export function initPostCaseApi({ caseService, router, userActionService }: RouteDeps) { router.post( { - path: '/api/cases', + path: CASES_URL, validate: { body: escapeHatch, }, diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.ts b/x-pack/plugins/case/server/routes/api/cases/push_case.ts index aff057adea37f..94ebe24c3d2ae 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/push_case.ts @@ -15,6 +15,7 @@ import { flattenCaseSavedObject, wrapError, escapeHatch } from '../utils'; import { CaseExternalServiceRequestRt, CaseResponseRt, throwErrors } from '../../../../common/api'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; +import { CASE_DETAILS_URL } from '../../../../common/constants'; export function initPushCaseUserActionApi({ caseConfigureService, @@ -24,7 +25,7 @@ export function initPushCaseUserActionApi({ }: RouteDeps) { router.post( { - path: '/api/cases/{case_id}/_push', + path: `${CASE_DETAILS_URL}/_push`, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/case/server/routes/api/cases/reporters/get_reporters.ts b/x-pack/plugins/case/server/routes/api/cases/reporters/get_reporters.ts index 56862a96e0563..3fc96f506d175 100644 --- a/x-pack/plugins/case/server/routes/api/cases/reporters/get_reporters.ts +++ b/x-pack/plugins/case/server/routes/api/cases/reporters/get_reporters.ts @@ -7,11 +7,12 @@ import { UsersRt } from '../../../../../common/api'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_REPORTERS_URL } from '../../../../../common/constants'; export function initGetReportersApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/reporters', + path: CASE_REPORTERS_URL, validate: {}, }, async (context, request, response) => { diff --git a/x-pack/plugins/case/server/routes/api/cases/status/get_status.ts b/x-pack/plugins/case/server/routes/api/cases/status/get_status.ts index f7431729d398c..8f86dbc91f315 100644 --- a/x-pack/plugins/case/server/routes/api/cases/status/get_status.ts +++ b/x-pack/plugins/case/server/routes/api/cases/status/get_status.ts @@ -9,11 +9,12 @@ import { wrapError } from '../../utils'; import { CasesStatusResponseRt } from '../../../../../common/api'; import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; +import { CASE_STATUS_URL } from '../../../../../common/constants'; export function initGetCasesStatusApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/status', + path: CASE_STATUS_URL, validate: {}, }, async (context, request, response) => { diff --git a/x-pack/plugins/case/server/routes/api/cases/tags/get_tags.ts b/x-pack/plugins/case/server/routes/api/cases/tags/get_tags.ts index 55e8fe2af128c..1a3da659c58c4 100644 --- a/x-pack/plugins/case/server/routes/api/cases/tags/get_tags.ts +++ b/x-pack/plugins/case/server/routes/api/cases/tags/get_tags.ts @@ -6,11 +6,12 @@ import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_TAGS_URL } from '../../../../../common/constants'; export function initGetTagsApi({ caseService, router }: RouteDeps) { router.get( { - path: '/api/cases/tags', + path: CASE_TAGS_URL, validate: {}, }, async (context, request, response) => { diff --git a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts index 2d4f16e46d561..c90979f60d23f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts @@ -10,11 +10,12 @@ import { CaseUserActionsResponseRt } from '../../../../../common/api'; import { CASE_SAVED_OBJECT, CASE_COMMENT_SAVED_OBJECT } from '../../../../saved_object_types'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; +import { CASE_USER_ACTIONS_URL } from '../../../../../common/constants'; export function initGetAllUserActionsApi({ userActionService, router }: RouteDeps) { router.get( { - path: '/api/cases/{case_id}/user_actions', + path: CASE_USER_ACTIONS_URL, validate: { params: schema.object({ case_id: schema.string(), diff --git a/x-pack/plugins/logstash/kibana.json b/x-pack/plugins/logstash/kibana.json index bcc926535d3c2..97dbf58865a88 100644 --- a/x-pack/plugins/logstash/kibana.json +++ b/x-pack/plugins/logstash/kibana.json @@ -4,9 +4,13 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "logstash"], "requiredPlugins": [ - "licensing" + "licensing", + "management" + ], + "optionalPlugins": [ + "home", + "security" ], - "optionalPlugins": ["security"], "server": true, - "ui": false + "ui": true } diff --git a/x-pack/legacy/plugins/logstash/public/sections/breadcrumbs.js b/x-pack/plugins/logstash/public/application/breadcrumbs.js similarity index 80% rename from x-pack/legacy/plugins/logstash/public/sections/breadcrumbs.js rename to x-pack/plugins/logstash/public/application/breadcrumbs.js index 3121a58ff6a74..322b9860b3785 100644 --- a/x-pack/legacy/plugins/logstash/public/sections/breadcrumbs.js +++ b/x-pack/plugins/logstash/public/application/breadcrumbs.js @@ -5,11 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; export function getPipelineListBreadcrumbs() { return [ - MANAGEMENT_BREADCRUMB, { text: i18n.translate('xpack.logstash.pipelines.listBreadcrumb', { defaultMessage: 'Pipelines', @@ -19,12 +17,11 @@ export function getPipelineListBreadcrumbs() { ]; } -export function getPipelineEditBreadcrumbs($route) { - const { pipeline } = $route.current.locals; +export function getPipelineEditBreadcrumbs(pipelineId) { return [ ...getPipelineListBreadcrumbs(), { - text: pipeline.id, + text: pipelineId, }, ]; } diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/flex_item_setting.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/flex_item_setting.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/flex_item_setting.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/flex_item_setting.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/form_label_with_icon_tip.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/form_label_with_icon_tip.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/form_label_with_icon_tip.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/form_label_with_icon_tip.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/confirm_delete_pipeline_modal.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/confirm_delete_pipeline_modal.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/confirm_delete_pipeline_modal.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/confirm_delete_pipeline_modal.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/constants.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/constants.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/constants.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/constants.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/flex_item_setting.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/flex_item_setting.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/flex_item_setting.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/flex_item_setting.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/flex_item_setting.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/flex_item_setting.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/flex_item_setting.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/flex_item_setting.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/form_label_with_icon_tip.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/form_label_with_icon_tip.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/form_label_with_icon_tip.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/form_label_with_icon_tip.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/form_label_with_icon_tip.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/form_label_with_icon_tip.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/form_label_with_icon_tip.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/form_label_with_icon_tip.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/index.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/index.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/index.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js similarity index 97% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js index 5e430ccbd8ceb..e45820d56cc03 100644 --- a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js @@ -13,7 +13,7 @@ import 'brace/mode/plain_text'; import 'brace/theme/github'; import { isEmpty } from 'lodash'; -import { TOOLTIPS } from '../../../../../../plugins/logstash/common/constants/tooltips'; +import { TOOLTIPS } from '../../../../common/constants/tooltips'; import { EuiButton, EuiButtonEmpty, @@ -40,7 +40,6 @@ class PipelineEditorUi extends React.Component { const { pipeline: { id, description, pipeline, settings }, - username, } = this.props; const pipelineWorkersSet = typeof settings['pipeline.workers'] === 'number'; @@ -60,7 +59,6 @@ class PipelineEditorUi extends React.Component { 'queue.max_bytes': settings['queue.max_bytes.number'] + settings['queue.max_bytes.units'], 'queue.type': settings['queue.type'], }, - username, }, pipelineIdErrors: [], pipelineIdPattern: /^[A-Za-z\_][A-Za-z0-9\-\_]*$/, @@ -236,15 +234,7 @@ class PipelineEditorUi extends React.Component { }; getPipelineHeadingText = () => { - const { - routeService: { - current: { - params: { clone, id }, - }, - }, - isNewPipeline, - intl, - } = this.props; + const { clone, id, isNewPipeline, intl } = this.props; if (!!clone && id) { return intl.formatMessage( @@ -502,6 +492,8 @@ class PipelineEditorUi extends React.Component { } PipelineEditorUi.propTypes = { + id: PropTypes.string, + clone: PropTypes.bool.isRequired, close: PropTypes.func.isRequired, isNewPipeline: PropTypes.bool.isRequired, licenseService: PropTypes.shape({ @@ -527,20 +519,11 @@ PipelineEditorUi.propTypes = { deletePipeline: PropTypes.func.isRequired, savePipeline: PropTypes.func.isRequired, }).isRequired, - routeService: PropTypes.shape({ - current: PropTypes.shape({ - params: PropTypes.shape({ - clone: PropTypes.oneOf([true, undefined]), - id: PropTypes.string, - }), - }), - }).isRequired, toastNotifications: PropTypes.shape({ addWarning: PropTypes.func.isRequired, addSuccess: PropTypes.func.isRequired, addError: PropTypes.func.isRequired, }).isRequired, - username: PropTypes.string, }; export const PipelineEditor = injectI18n(PipelineEditorUi); diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js similarity index 96% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js index 2d7ed5f257fbd..bb5961ce36120 100644 --- a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js @@ -17,7 +17,6 @@ describe('PipelineEditor component', () => { let open; let pipeline; let pipelineService; - let routeService; let toastNotifications; let username; @@ -47,14 +46,6 @@ describe('PipelineEditor component', () => { deletePipeline: jest.fn(), savePipeline: jest.fn(), }; - routeService = { - current: { - params: { - clone: undefined, - id: undefined, - }, - }, - }; toastNotifications = { addWarning: jest.fn(), addSuccess: jest.fn(), @@ -62,13 +53,14 @@ describe('PipelineEditor component', () => { }; username = 'elastic'; props = { + clone: false, + id: 'pipelineId', close, isNewPipeline, licenseService, open, pipeline, pipelineService, - routeService, toastNotifications, username, }; @@ -79,10 +71,8 @@ describe('PipelineEditor component', () => { }); it('matches snapshot for clone pipeline', () => { - routeService.current.params = { - clone: true, - id: 'pipelineToClone', - }; + props.clone = true; + props.id = 'pipelineToClone'; expect(shallowWithIntl()).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/add_role_alert.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/add_role_alert.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/add_role_alert.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/add_role_alert.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/alert_call_out.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/alert_call_out.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/alert_call_out.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/alert_call_out.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/enable_monitoring_alert.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/enable_monitoring_alert.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/enable_monitoring_alert.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/enable_monitoring_alert.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/info_alerts.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/info_alerts.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/info_alerts.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/info_alerts.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/pipelines_table.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/pipelines_table.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/__snapshots__/pipelines_table.test.js.snap rename to x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/pipelines_table.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/add_role_alert.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/add_role_alert.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/add_role_alert.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/add_role_alert.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/alert_call_out.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/alert_call_out.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/alert_call_out.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/alert_call_out.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/confirm_delete_modal.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/confirm_delete_modal.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/confirm_delete_modal.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/confirm_delete_modal.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/constants.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/constants.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/constants.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/constants.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/enable_monitoring_alert.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/enable_monitoring_alert.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/enable_monitoring_alert.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/enable_monitoring_alert.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/index.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/index.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/index.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/info_alerts.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/info_alerts.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/info_alerts.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/info_alerts.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/info_alerts.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/info_alerts.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/info_alerts.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/info_alerts.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipeline_list.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipeline_list.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipeline_list.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipeline_list.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipelines_table.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipelines_table.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.js diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipelines_table.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/pipeline_list/pipelines_table.test.js rename to x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap b/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/__snapshots__/upgrade_failure_actions.test.js.snap b/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure_actions.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/__snapshots__/upgrade_failure_actions.test.js.snap rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure_actions.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/__snapshots__/upgrade_failure_title.test.js.snap b/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure_title.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/__snapshots__/upgrade_failure_title.test.js.snap rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure_title.test.js.snap diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/constants.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/constants.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/constants.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/constants.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/index.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/index.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/index.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure.test.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure.test.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_actions.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_actions.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_actions.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_actions.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_actions.test.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_actions.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_actions.test.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_actions.test.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_title.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_title.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_title.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_title.js diff --git a/x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_title.test.js b/x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_title.test.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/components/upgrade_failure/upgrade_failure_title.test.js rename to x-pack/plugins/logstash/public/application/components/upgrade_failure/upgrade_failure_title.test.js diff --git a/x-pack/plugins/logstash/public/application/index.tsx b/x-pack/plugins/logstash/public/application/index.tsx new file mode 100644 index 0000000000000..438038d6c885e --- /dev/null +++ b/x-pack/plugins/logstash/public/application/index.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; + +import { CoreStart } from 'src/core/public'; +import { ManagementAppMountParams } from '../../../../../src/plugins/management/public'; +import { + ClusterService, + MonitoringService, + PipelineService, + PipelinesService, + UpgradeService, + // @ts-ignore +} from '../services'; +// @ts-ignore +import { PipelineList } from './components/pipeline_list'; +import { PipelineEditView } from './pipeline_edit_view'; +// @ts-ignore +import { Pipeline } from '../models/pipeline'; +// @ts-ignore +import * as Breadcrumbs from './breadcrumbs'; + +export const renderApp = async ( + core: CoreStart, + { basePath, element, setBreadcrumbs }: ManagementAppMountParams, + licenseService$: Observable +) => { + const logstashLicenseService = await licenseService$.pipe(first()).toPromise(); + const clusterService = new ClusterService(core.http); + const monitoringService = new MonitoringService( + core.http, + // When monitoring is migrated this should be fetched from monitoring's plugin contract + core.injectedMetadata.getInjectedVar('monitoringUiEnabled'), + clusterService + ); + const pipelinesService = new PipelinesService(core.http, monitoringService); + const pipelineService = new PipelineService(core.http, pipelinesService); + const upgradeService = new UpgradeService(core.http); + + ReactDOM.render( + + + + { + setBreadcrumbs(Breadcrumbs.getPipelineListBreadcrumbs()); + return ( + history.push(`/pipeline/${id}/edit`)} + clonePipeline={(id: string) => history.push(`/pipeline/${id}/edit?clone`)} + createPipeline={() => history.push(`/pipeline/new-pipeline`)} + pipelinesService={pipelinesService} + toastNotifications={core.notifications.toasts} + /> + ); + }} + /> + ( + + )} + /> + } + /> + ( + + )} + /> + + + , + element + ); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +}; diff --git a/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx b/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx new file mode 100644 index 0000000000000..c1b465febcd9b --- /dev/null +++ b/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useLayoutEffect, useCallback } from 'react'; +import { usePromise } from 'react-use'; +import { History } from 'history'; + +import { i18n } from '@kbn/i18n'; +import { ToastsStart } from 'src/core/public'; + +// @ts-ignore +import { UpgradeFailure } from './components/upgrade_failure'; +// @ts-ignore +import { PipelineEditor } from './components/pipeline_editor'; +// @ts-ignore +import { Pipeline } from '../models/pipeline'; +import { ManagementAppMountParams } from '../../../../../src/plugins/management/public'; +// @ts-ignore +import * as Breadcrumbs from './breadcrumbs'; + +const usePipeline = ( + pipelineService: any, + logstashLicenseService: any, + toasts: ToastsStart, + shouldClone: boolean, + id?: string +) => { + const mounted = usePromise(); + const [pipeline, setPipeline] = useState(null); + + useLayoutEffect(() => { + (async () => { + if (!id) { + return setPipeline(new Pipeline()); + } + + try { + const result = await mounted(pipelineService.loadPipeline(id) as Promise); + setPipeline(shouldClone ? result.clone : result); + } catch (e) { + await logstashLicenseService.checkValidity(); + if (e.status !== 403) { + toasts.addDanger( + i18n.translate('xpack.logstash.couldNotLoadPipelineErrorNotification', { + defaultMessage: `Couldn't load pipeline. Error: '{errStatusText}'.`, + values: { + errStatusText: e.statusText, + }, + }) + ); + } + } + })(); + }, [pipelineService, id, mounted, shouldClone, logstashLicenseService, toasts]); + + return pipeline; +}; + +const useIsUpgraded = (upgradeService: any) => { + const [isUpgraded, setIsUpgraded] = useState(null); + const mounted = usePromise(); + + useLayoutEffect(() => { + mounted(upgradeService.executeUpgrade() as Promise).then(result => + setIsUpgraded(result) + ); + }, [mounted, upgradeService]); + + return isUpgraded; +}; + +interface EditProps { + pipelineService: any; + logstashLicenseService: any; + upgradeService: any; + toasts: ToastsStart; + history: History; + setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; + + // URL params + id?: string; +} + +export const PipelineEditView: React.FC = ({ + pipelineService, + logstashLicenseService, + upgradeService, + toasts, + history, + setBreadcrumbs, + id, +}) => { + const params = new URLSearchParams(history.location.search); + const shouldRetry = params.get('retry') === 'true'; + const shouldClone = params.get('clone') === ''; + + const pipeline = usePipeline(pipelineService, logstashLicenseService, toasts, shouldClone, id); + const isUpgraded = useIsUpgraded(upgradeService); + + const onRetry = useCallback(() => { + const newParams = new URLSearchParams(history.location.search); + newParams.set('retry', 'true'); + history.replace({ search: newParams.toString() }); + }, [history]); + const close = useCallback(() => { + history.push('/'); + }, [history]); + const open = useCallback( + (newId: string) => { + history.push(`/pipeline/${newId}/edit`); + }, + [history] + ); + + if (!pipeline || isUpgraded === null) { + return null; + } + + const isNewPipeline = !pipeline.id; + setBreadcrumbs( + isNewPipeline + ? Breadcrumbs.getPipelineCreateBreadcrumbs() + : Breadcrumbs.getPipelineEditBreadcrumbs(pipeline.id) + ); + + if (!isUpgraded) { + return ( + + ); + } + + return ( + + ); +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/index.ts b/x-pack/plugins/logstash/public/index.ts similarity index 72% rename from x-pack/plugins/snapshot_restore/public/application/lib/authorization/index.ts rename to x-pack/plugins/logstash/public/index.ts index 73bbde465146c..26a1ca4e8c6c4 100644 --- a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/index.ts +++ b/x-pack/plugins/logstash/public/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './components'; +import { LogstashPlugin } from './plugin'; + +export const plugin = () => new LogstashPlugin(); diff --git a/x-pack/legacy/plugins/logstash/public/lib/get_search_value/get_search_value.js b/x-pack/plugins/logstash/public/lib/get_search_value/get_search_value.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/lib/get_search_value/get_search_value.js rename to x-pack/plugins/logstash/public/lib/get_search_value/get_search_value.js diff --git a/x-pack/legacy/plugins/logstash/public/lib/get_search_value/index.js b/x-pack/plugins/logstash/public/lib/get_search_value/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/lib/get_search_value/index.js rename to x-pack/plugins/logstash/public/lib/get_search_value/index.js diff --git a/x-pack/legacy/plugins/logstash/public/models/cluster/cluster.js b/x-pack/plugins/logstash/public/models/cluster/cluster.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/models/cluster/cluster.js rename to x-pack/plugins/logstash/public/models/cluster/cluster.js diff --git a/x-pack/legacy/plugins/logstash/public/models/cluster/index.js b/x-pack/plugins/logstash/public/models/cluster/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/models/cluster/index.js rename to x-pack/plugins/logstash/public/models/cluster/index.js diff --git a/x-pack/legacy/plugins/logstash/public/models/pipeline/index.js b/x-pack/plugins/logstash/public/models/pipeline/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/models/pipeline/index.js rename to x-pack/plugins/logstash/public/models/pipeline/index.js diff --git a/x-pack/legacy/plugins/logstash/public/models/pipeline/pipeline.js b/x-pack/plugins/logstash/public/models/pipeline/pipeline.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/models/pipeline/pipeline.js rename to x-pack/plugins/logstash/public/models/pipeline/pipeline.js diff --git a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/index.js b/x-pack/plugins/logstash/public/models/pipeline_list_item/index.js similarity index 100% rename from x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/index.js rename to x-pack/plugins/logstash/public/models/pipeline_list_item/index.js diff --git a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js b/x-pack/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js similarity index 83% rename from x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js rename to x-pack/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js index 06d01a05bac27..3a304e467e0c0 100755 --- a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js +++ b/x-pack/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js @@ -5,10 +5,10 @@ */ import { pick, capitalize } from 'lodash'; +import moment from 'moment'; -import { getSearchValue } from 'plugins/logstash/lib/get_search_value'; -import { getMoment } from 'plugins/logstash/../common/lib/get_moment'; -import { PIPELINE } from '../../../../../../plugins/logstash/common/constants'; +import { getSearchValue } from '../../lib/get_search_value'; +import { PIPELINE } from '../../../common/constants'; /** * Represents the model for listing pipelines in the UI @@ -25,7 +25,7 @@ export class PipelineListItem { this.username = props.username; if (props.lastModified) { - this.lastModified = getMoment(props.lastModified); + this.lastModified = getMomentDate(props.lastModified); this.lastModifiedHumanized = capitalize(this.lastModified.fromNow()); } } @@ -51,3 +51,11 @@ export class PipelineListItem { return new PipelineListItem(props); } } + +function getMomentDate(date) { + if (!date) { + return null; + } + + return moment(date); +} diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts new file mode 100644 index 0000000000000..91d1a39d3970c --- /dev/null +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -0,0 +1,92 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { once } from 'lodash'; + +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { + HomePublicPluginSetup, + FeatureCatalogueCategory, +} from '../../../../src/plugins/home/public'; +import { LicensingPluginSetup } from '../../licensing/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; + +// @ts-ignore +import { LogstashLicenseService } from './services'; + +interface SetupDeps { + licensing: LicensingPluginSetup; + management: ManagementSetup; + + home?: HomePublicPluginSetup; +} + +export class LogstashPlugin implements Plugin { + private licenseSubscription?: Subscription; + + public setup(core: CoreSetup, plugins: SetupDeps) { + const logstashLicense$ = plugins.licensing.license$.pipe( + map(license => new LogstashLicenseService(license)) + ); + const section = plugins.management.sections.register({ + id: 'logstash', + title: 'Logstash', + order: 30, + euiIconType: 'logoLogstash', + }); + const managementApp = section.registerApp({ + id: 'pipelines', + title: i18n.translate('xpack.logstash.managementSection.pipelinesTitle', { + defaultMessage: 'Pipelines', + }), + order: 10, + mount: async params => { + const [coreStart] = await core.getStartServices(); + const { renderApp } = await import('./application'); + + return renderApp(coreStart, params, logstashLicense$); + }, + }); + + this.licenseSubscription = logstashLicense$.subscribe((license: any) => { + if (license.enableLinks) { + managementApp.enable(); + } else { + managementApp.disable(); + } + + if (plugins.home && license.enableLinks) { + // Ensure that we don't register the feature more than once + once(() => { + plugins.home!.featureCatalogue.register({ + id: 'management_logstash', + title: i18n.translate('xpack.logstash.homeFeature.logstashPipelinesTitle', { + defaultMessage: 'Logstash Pipelines', + }), + description: i18n.translate('xpack.logstash.homeFeature.logstashPipelinesDescription', { + defaultMessage: 'Create, delete, update, and clone data ingestion pipelines.', + }), + icon: 'pipelineApp', + path: '/app/kibana#/management/logstash/pipelines', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }); + }); + } + }); + } + + public start(core: CoreStart) {} + + public stop() { + if (this.licenseSubscription) { + this.licenseSubscription.unsubscribe(); + } + } +} diff --git a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js b/x-pack/plugins/logstash/public/services/cluster/cluster_service.js similarity index 51% rename from x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js rename to x-pack/plugins/logstash/public/services/cluster/cluster_service.js index e89c2fe7d11bf..20f3b0d349c80 100755 --- a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js +++ b/x-pack/plugins/logstash/public/services/cluster/cluster_service.js @@ -4,22 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; -import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; -import { Cluster } from 'plugins/logstash/models/cluster'; +import { ROUTES } from '../../../common/constants'; +import { Cluster } from '../../models/cluster'; export class ClusterService { - constructor($http) { - this.$http = $http; - this.basePath = chrome.addBasePath(ROUTES.API_ROOT); + constructor(http) { + this.http = http; } loadCluster() { - return this.$http.get(`${this.basePath}/cluster`).then(response => { - if (!response.data) { + return this.http.get(`${ROUTES.API_ROOT}/cluster`).then(response => { + if (!response) { return; } - return Cluster.fromUpstreamJSON(response.data.cluster); + return Cluster.fromUpstreamJSON(response.cluster); }); } diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/index.js b/x-pack/plugins/logstash/public/services/cluster/index.js similarity index 82% rename from x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/index.js rename to x-pack/plugins/logstash/public/services/cluster/index.js index 5889bbdf96a93..4417262d9f442 100755 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/index.js +++ b/x-pack/plugins/logstash/public/services/cluster/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './pipeline_edit'; +export { ClusterService } from './cluster_service'; diff --git a/x-pack/plugins/logstash/public/services/index.js b/x-pack/plugins/logstash/public/services/index.js new file mode 100644 index 0000000000000..a7e8aa5c6259f --- /dev/null +++ b/x-pack/plugins/logstash/public/services/index.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ClusterService } from './cluster'; +export { LogstashLicenseService } from './license'; +export { MonitoringService } from './monitoring'; +export { PipelineService } from './pipeline'; +export { PipelinesService } from './pipelines'; +export { UpgradeService } from './upgrade'; diff --git a/x-pack/legacy/plugins/logstash/public/lib/update_management_sections/index.js b/x-pack/plugins/logstash/public/services/license/index.js similarity index 77% rename from x-pack/legacy/plugins/logstash/public/lib/update_management_sections/index.js rename to x-pack/plugins/logstash/public/services/license/index.js index 9d53d4dd61163..64f39b1144cee 100755 --- a/x-pack/legacy/plugins/logstash/public/lib/update_management_sections/index.js +++ b/x-pack/plugins/logstash/public/services/license/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { updateLogstashSections } from './update_logstash_sections'; +export { LogstashLicenseService } from './logstash_license_service'; diff --git a/x-pack/plugins/logstash/public/services/license/logstash_license_service.js b/x-pack/plugins/logstash/public/services/license/logstash_license_service.js new file mode 100755 index 0000000000000..b836b75b89cc7 --- /dev/null +++ b/x-pack/plugins/logstash/public/services/license/logstash_license_service.js @@ -0,0 +1,106 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export class LogstashLicenseService { + constructor(license, navigateToApp, toasts) { + this.license = license; + this.navigateToApp = navigateToApp; + this.toasts = toasts; + } + + get enableLinks() { + return this.calculated.enableLinks; + } + + get isAvailable() { + return this.calculated.isAvailable; + } + + get isReadOnly() { + return this.calculated.isReadOnly; + } + + get message() { + return this.calculated.message; + } + + get isSecurityEnabled() { + return this.license.getFeature(`security`).isEnabled; + } + + /** + * Checks if the license is valid or the license can perform downgraded UI tasks. + * Rejects if the plugin is not available due to license. + */ + checkValidity() { + return new Promise((resolve, reject) => { + if (this.isAvailable) { + return resolve(); + } + + return reject(); + }); + } + + get calculated() { + if (!this.license) { + throw new Error(`No license available!`); + } + + if (!this.isSecurityEnabled) { + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message: i18n.translate('xpack.logstash.managementSection.enableSecurityDescription', { + defaultMessage: + 'Security must be enabled in order to use Logstash pipeline management features.' + + ' Please set xpack.security.enabled: true in your elasticsearch.yml.', + }), + }; + } + + if (!this.license.hasAtLeast('standard')) { + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message: i18n.translate( + 'xpack.logstash.managementSection.licenseDoesNotSupportDescription', + { + defaultMessage: + 'Your {licenseType} license does not support Logstash pipeline management features. Please upgrade your license.', + values: { licenseType: this.license.type }, + } + ), + }; + } + + if (!this.license.isActive) { + return { + isAvailable: true, + enableLinks: true, + isReadonly: true, + message: i18n.translate( + 'xpack.logstash.managementSection.pipelineCrudOperationsNotAllowedDescription', + { + defaultMessage: + 'You cannot edit, create, or delete your Logstash pipelines because your {licenseType} license has expired.', + values: { licenseType: this.license.type }, + } + ), + }; + } + + return { + isAvailable: true, + enableLinks: true, + isReadOnly: false, + }; + } +} diff --git a/x-pack/plugins/logstash/public/services/monitoring/index.js b/x-pack/plugins/logstash/public/services/monitoring/index.js new file mode 100755 index 0000000000000..bc0e8b6bc978a --- /dev/null +++ b/x-pack/plugins/logstash/public/services/monitoring/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { MonitoringService } from './monitoring_service'; diff --git a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js b/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js similarity index 58% rename from x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js rename to x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js index 6103e730c2171..d551f4fba61d2 100755 --- a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js +++ b/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js @@ -5,17 +5,14 @@ */ import moment from 'moment'; -import chrome from 'ui/chrome'; -import { ROUTES, MONITORING } from '../../../../../../plugins/logstash/common/constants'; -import { PipelineListItem } from 'plugins/logstash/models/pipeline_list_item'; +import { ROUTES, MONITORING } from '../../../common/constants'; +import { PipelineListItem } from '../../models/pipeline_list_item'; export class MonitoringService { - constructor($http, Promise, monitoringUiEnabled, clusterService) { - this.$http = $http; - this.Promise = Promise; + constructor(http, monitoringUiEnabled, clusterService) { + this.http = http; this.monitoringUiEnabled = monitoringUiEnabled; this.clusterService = clusterService; - this.basePath = chrome.addBasePath(ROUTES.MONITORING_API_ROOT); } isMonitoringEnabled() { @@ -30,18 +27,18 @@ export class MonitoringService { return this.clusterService .loadCluster() .then(cluster => { - const url = `${this.basePath}/v1/clusters/${cluster.uuid}/logstash/pipeline_ids`; + const url = `${ROUTES.MONITORING_API_ROOT}/v1/clusters/${cluster.uuid}/logstash/pipeline_ids`; const now = moment.utc(); - const body = { + const body = JSON.stringify({ timeRange: { max: now.toISOString(), min: now.subtract(MONITORING.ACTIVE_PIPELINE_RANGE_S, 'seconds').toISOString(), }, - }; - return this.$http.post(url, body); + }); + return this.http.post(url, { body }); }) .then(response => - response.data.map(pipeline => PipelineListItem.fromUpstreamMonitoringJSON(pipeline)) + response.map(pipeline => PipelineListItem.fromUpstreamMonitoringJSON(pipeline)) ) .catch(() => []); } diff --git a/x-pack/legacy/plugins/logstash/common/lib/index.js b/x-pack/plugins/logstash/public/services/pipeline/index.js similarity index 81% rename from x-pack/legacy/plugins/logstash/common/lib/index.js rename to x-pack/plugins/logstash/public/services/pipeline/index.js index 6ed1d24a37791..70d228b34860b 100755 --- a/x-pack/legacy/plugins/logstash/common/lib/index.js +++ b/x-pack/plugins/logstash/public/services/pipeline/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { getMoment } from './get_moment'; +export { PipelineService } from './pipeline_service'; diff --git a/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.js b/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.js new file mode 100755 index 0000000000000..7c3e18e745d82 --- /dev/null +++ b/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.js @@ -0,0 +1,40 @@ +/* + * 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 { ROUTES } from '../../../common/constants'; +import { Pipeline } from '../../models/pipeline'; + +export class PipelineService { + constructor(http, pipelinesService) { + this.http = http; + this.pipelinesService = pipelinesService; + } + + loadPipeline(id) { + return this.http.get(`${ROUTES.API_ROOT}/pipeline/${id}`).then(response => { + return Pipeline.fromUpstreamJSON(response); + }); + } + + savePipeline(pipelineModel) { + return this.http + .put(`${ROUTES.API_ROOT}/pipeline/${pipelineModel.id}`, { + body: JSON.stringify(pipelineModel.upstreamJSON), + }) + .catch(e => { + throw e.message; + }); + } + + deletePipeline(id) { + return this.http + .delete(`${ROUTES.API_ROOT}/pipeline/${id}`) + .then(() => this.pipelinesService.addToRecentlyDeleted(id)) + .catch(e => { + throw e.message; + }); + } +} diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/index.js b/x-pack/plugins/logstash/public/services/pipelines/index.js similarity index 81% rename from x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/index.js rename to x-pack/plugins/logstash/public/services/pipelines/index.js index 4b699ed79cd26..a932dd4b951f4 100755 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/index.js +++ b/x-pack/plugins/logstash/public/services/pipelines/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './pipeline_edit_route'; +export { PipelinesService } from './pipelines_service'; diff --git a/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.js b/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.js new file mode 100755 index 0000000000000..00610a23f2717 --- /dev/null +++ b/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.js @@ -0,0 +1,128 @@ +/* + * 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 { ROUTES, MONITORING } from '../../../common/constants'; +import { PipelineListItem } from '../../models/pipeline_list_item'; + +const RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY = 'xpack.logstash.recentlyDeletedPipelines'; + +export class PipelinesService { + constructor(http, monitoringService) { + this.http = http; + this.monitoringService = monitoringService; + } + + getPipelineList() { + return Promise.all([this.getManagementPipelineList(), this.getMonitoringPipelineList()]).then( + ([managementPipelines, monitoringPipelines]) => { + const now = Date.now(); + + // Monitoring will report centrally-managed pipelines as well, including recently-deleted centrally-managed ones. + // If there's a recently-deleted pipeline we're keeping track of BUT monitoring doesn't report it, that means + // it's not running in Logstash any more. So we can stop tracking it as a recently-deleted pipeline. + const monitoringPipelineIds = monitoringPipelines.map(pipeline => pipeline.id); + this.getRecentlyDeleted().forEach(recentlyDeletedPipeline => { + // We don't want to stop tracking the recently-deleted pipeline until Monitoring has had some + // time to report on it. Otherwise, if we stop tracking first, *then* Monitoring reports it, we'll + // still end up showing it in the list until Monitoring stops reporting it. + if (now - recentlyDeletedPipeline.deletedOn < MONITORING.ACTIVE_PIPELINE_RANGE_S * 1000) { + return; + } + + // If Monitoring is still reporting the pipeline, don't stop tracking it yet + if (monitoringPipelineIds.includes(recentlyDeletedPipeline.id)) { + return; + } + + this.removeFromRecentlyDeleted(recentlyDeletedPipeline.id); + }); + + // Merge centrally-managed pipelines with pipelines reported by monitoring. Take care to dedupe + // while merging because monitoring will (rightly) report centrally-managed pipelines as well, + // including recently-deleted ones! + const managementPipelineIds = managementPipelines.map(pipeline => pipeline.id); + return managementPipelines.concat( + monitoringPipelines.filter( + monitoringPipeline => + !managementPipelineIds.includes(monitoringPipeline.id) && + !this.isRecentlyDeleted(monitoringPipeline.id) + ) + ); + } + ); + } + + getManagementPipelineList() { + return this.http.get(`${ROUTES.API_ROOT}/pipelines`).then(response => { + return response.pipelines.map(pipeline => PipelineListItem.fromUpstreamJSON(pipeline)); + }); + } + + getMonitoringPipelineList() { + return this.monitoringService.getPipelineList(); + } + + /** + * Delete a collection of pipelines + * + * @param pipelineIds Array of pipeline IDs + * @return Promise { numSuccesses, numErrors } + */ + deletePipelines(pipelineIds) { + const body = JSON.stringify({ + pipelineIds, + }); + return this.http.post(`${ROUTES.API_ROOT}/pipelines/delete`, { body }).then(response => { + this.addToRecentlyDeleted(...pipelineIds); + return response.results; + }); + } + + addToRecentlyDeleted(...pipelineIds) { + const recentlyDeletedPipelines = this.getRecentlyDeleted(); + const recentlyDeletedPipelineIds = recentlyDeletedPipelines.map(pipeline => pipeline.id); + pipelineIds.forEach(pipelineId => { + if (!recentlyDeletedPipelineIds.includes(pipelineId)) { + recentlyDeletedPipelines.push({ + id: pipelineId, + deletedOn: Date.now(), + }); + } + }); + this.setRecentlyDeleted(recentlyDeletedPipelines); + } + + removeFromRecentlyDeleted(...pipelineIds) { + const recentlyDeletedPipelinesToKeep = this.getRecentlyDeleted().filter( + recentlyDeletedPipeline => !pipelineIds.includes(recentlyDeletedPipeline.id) + ); + this.setRecentlyDeleted(recentlyDeletedPipelinesToKeep); + } + + isRecentlyDeleted(pipelineId) { + return this.getRecentlyDeleted() + .map(pipeline => pipeline.id) + .includes(pipelineId); + } + + getRecentlyDeleted() { + const recentlyDeletedPipelines = window.localStorage.getItem( + RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY + ); + if (!recentlyDeletedPipelines) { + return []; + } + + return JSON.parse(recentlyDeletedPipelines); + } + + setRecentlyDeleted(recentlyDeletedPipelineIds) { + window.localStorage.setItem( + RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY, + JSON.stringify(recentlyDeletedPipelineIds) + ); + } +} diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/index.js b/x-pack/plugins/logstash/public/services/upgrade/index.js similarity index 82% rename from x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/index.js rename to x-pack/plugins/logstash/public/services/upgrade/index.js index 3a9a6b860c51f..1c835b11ae423 100755 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/index.js +++ b/x-pack/plugins/logstash/public/services/upgrade/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './upgrade_failure'; +export { UpgradeService } from './upgrade_service'; diff --git a/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.js b/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.js new file mode 100755 index 0000000000000..7bd101ebee6b0 --- /dev/null +++ b/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.js @@ -0,0 +1,22 @@ +/* + * 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 { ROUTES } from '../../../common/constants'; + +export class UpgradeService { + constructor(http) { + this.http = http; + } + + executeUpgrade() { + return this.http + .post(`${ROUTES.API_ROOT}/upgrade`) + .then(response => response.is_upgraded) + .catch(e => { + throw e.message; + }); + } +} diff --git a/x-pack/plugins/logstash/server/routes/pipeline/save.ts b/x-pack/plugins/logstash/server/routes/pipeline/save.ts index 556c281944a85..e484d0e221b6d 100644 --- a/x-pack/plugins/logstash/server/routes/pipeline/save.ts +++ b/x-pack/plugins/logstash/server/routes/pipeline/save.ts @@ -25,7 +25,6 @@ export function registerPipelineSaveRoute(router: IRouter, security?: SecurityPl id: schema.string(), description: schema.string(), pipeline: schema.string(), - username: schema.string(), settings: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), }, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 8a5da8e859721..731fffcac1bb0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -86,7 +86,7 @@ export const sampleDocNoSortId = (someUuid: string = sampleIdGuid): SignalSource _id: someUuid, _source: { someKey: 'someValue', - '@timestamp': 'someTimeStamp', + '@timestamp': '2020-04-20T21:27:45+0000', }, }); @@ -97,7 +97,7 @@ export const sampleDocNoSortIdNoVersion = (someUuid: string = sampleIdGuid): Sig _id: someUuid, _source: { someKey: 'someValue', - '@timestamp': 'someTimeStamp', + '@timestamp': '2020-04-20T21:27:45+0000', }, }); @@ -109,7 +109,7 @@ export const sampleDocWithSortId = (someUuid: string = sampleIdGuid): SignalSour _id: someUuid, _source: { someKey: 'someValue', - '@timestamp': 'someTimeStamp', + '@timestamp': '2020-04-20T21:27:45+0000', }, sort: ['1234567891111'], }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts index bbd01cfaafc62..df9d282b71e5e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -59,7 +59,7 @@ describe('buildBulkBody', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', status: 'open', rule: { actions: [], @@ -185,7 +185,7 @@ describe('buildBulkBody', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', status: 'open', rule: { actions: [], @@ -309,7 +309,7 @@ describe('buildBulkBody', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', status: 'open', rule: { actions: [], @@ -426,7 +426,7 @@ describe('buildBulkBody', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', status: 'open', rule: { actions: [], diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts index 0a50c33fbbfe4..f3f4ab60e4db6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts @@ -41,7 +41,7 @@ describe('buildSignal', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', status: 'open', rule: { created_by: 'elastic', @@ -101,7 +101,7 @@ describe('buildSignal', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', original_event: { action: 'socket_opened', dataset: 'socket', @@ -173,7 +173,7 @@ describe('buildSignal', () => { depth: 1, }, ], - original_time: 'someTimeStamp', + original_time: '2020-04-20T21:27:45+0000', original_event: { action: 'socket_opened', dataset: 'socket', diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index cec011ae8c445..2cb23b05f6a9b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -30,7 +30,7 @@ describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -55,6 +55,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(success).toEqual(true); expect(createdSignalsCount).toEqual(0); + expect(lastLookBackDate).toBeNull(); }); test('if successful iteration of while loop with maxDocs', async () => { @@ -105,7 +106,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), ruleParams: sampleParams, services: mockService, @@ -130,13 +131,14 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(success).toEqual(true); expect(createdSignalsCount).toEqual(3); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); mockService.callCluster.mockResolvedValue(sampleBulkCreateDuplicateResult); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -161,6 +163,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); expect(createdSignalsCount).toEqual(1); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { @@ -179,7 +182,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -204,6 +207,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); expect(createdSignalsCount).toEqual(1); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { @@ -222,7 +226,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoHits(), ruleParams: sampleParams, services: mockService, @@ -246,6 +250,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(success).toEqual(true); expect(createdSignalsCount).toEqual(1); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { @@ -267,7 +272,7 @@ describe('searchAfterAndBulkCreate', () => { ], }) .mockResolvedValueOnce(sampleDocSearchResultsNoSortId()); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -291,6 +296,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(success).toEqual(true); expect(createdSignalsCount).toEqual(1); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { @@ -312,7 +318,7 @@ describe('searchAfterAndBulkCreate', () => { ], }) .mockResolvedValueOnce(sampleEmptyDocSearchResults()); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -336,6 +342,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(success).toEqual(true); expect(createdSignalsCount).toEqual(1); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); test('if returns false when singleSearchAfter throws an exception', async () => { @@ -359,7 +366,7 @@ describe('searchAfterAndBulkCreate', () => { .mockImplementation(() => { throw Error('Fake Error'); }); - const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -383,5 +390,6 @@ describe('searchAfterAndBulkCreate', () => { }); expect(success).toEqual(false); expect(createdSignalsCount).toEqual(1); + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index e287e33295c89..acf3e9bfb055c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -98,13 +98,15 @@ export const searchAfterAndBulkCreate = async ({ tags, throttle, }); - toReturn.lastLookBackDate = - someResult.hits.hits.length > 0 - ? new Date(someResult.hits.hits[someResult.hits.hits.length - 1]?._source['@timestamp']) - : null; - if (createdItemsCount) { + + if (createdItemsCount > 0) { toReturn.createdSignalsCount = createdItemsCount; + toReturn.lastLookBackDate = + someResult.hits.hits.length > 0 + ? new Date(someResult.hits.hits[someResult.hits.hits.length - 1]?._source['@timestamp']) + : null; } + if (bulkCreateDuration) { toReturn.bulkCreateTimes.push(bulkCreateDuration); } diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 51cc0f449b17a..6f3cc6e708fce 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -300,7 +300,7 @@ describe('singleBulkCreate', () => { _id: 'e1e08ddc-5e37-49ff-a258-5393aa44435a', _source: { someKey: 'someValue', - '@timestamp': 'someTimeStamp', + '@timestamp': '2020-04-20T21:27:45+0000', signal: { parent: { rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', @@ -334,7 +334,7 @@ describe('singleBulkCreate', () => { test('filter duplicate rules will return back search responses if they do not have a signal and will NOT filter the source out', () => { const ancestors = sampleDocWithAncestors(); - ancestors.hits.hits[0]._source = { '@timestamp': 'some timestamp' }; + ancestors.hits.hits[0]._source = { '@timestamp': '2020-04-20T21:27:45+0000' }; const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', ancestors); expect(filtered).toEqual([ { @@ -343,7 +343,7 @@ describe('singleBulkCreate', () => { _score: 100, _version: 1, _id: 'e1e08ddc-5e37-49ff-a258-5393aa44435a', - _source: { '@timestamp': 'some timestamp' }, + _source: { '@timestamp': '2020-04-20T21:27:45+0000' }, }, ]); }); diff --git a/x-pack/plugins/snapshot_restore/common/types/index.ts b/x-pack/plugins/snapshot_restore/common/types/index.ts index 5cb3839fa9e01..d52584ca737a2 100644 --- a/x-pack/plugins/snapshot_restore/common/types/index.ts +++ b/x-pack/plugins/snapshot_restore/common/types/index.ts @@ -8,4 +8,3 @@ export * from './repository'; export * from './snapshot'; export * from './restore'; export * from './policy'; -export * from './privileges'; diff --git a/x-pack/plugins/snapshot_restore/common/types/privileges.ts b/x-pack/plugins/snapshot_restore/common/types/privileges.ts deleted file mode 100644 index bf710b8225599..0000000000000 --- a/x-pack/plugins/snapshot_restore/common/types/privileges.ts +++ /dev/null @@ -1,14 +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. - */ - -export interface MissingPrivileges { - [key: string]: string[] | undefined; -} - -export interface Privileges { - hasAllPrivileges: boolean; - missingPrivileges: MissingPrivileges; -} diff --git a/x-pack/plugins/snapshot_restore/public/application/app.tsx b/x-pack/plugins/snapshot_restore/public/application/app.tsx index 77ef697814b2c..350d8aec711ed 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app.tsx @@ -4,13 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import { EuiPageContent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { APP_REQUIRED_CLUSTER_PRIVILEGES } from '../../common/constants'; -import { SectionLoading, SectionError } from './components'; +import { APP_REQUIRED_CLUSTER_PRIVILEGES } from '../../common'; +import { + useAuthorizationContext, + SectionError, + WithPrivileges, + NotAuthorizedSection, +} from '../shared_imports'; +import { SectionLoading } from './components'; import { BASE_PATH, DEFAULT_SECTION, Section } from './constants'; import { RepositoryAdd, @@ -21,11 +27,10 @@ import { PolicyEdit, } from './sections'; import { useConfig } from './app_context'; -import { AuthorizationContext, WithPrivileges, NotAuthorizedSection } from './lib/authorization'; export const App: React.FunctionComponent = () => { const { slm_ui: slmUi } = useConfig(); - const { apiError } = useContext(AuthorizationContext); + const { apiError } = useAuthorizationContext(); const sections: Section[] = ['repositories', 'snapshots', 'restore_status']; diff --git a/x-pack/plugins/snapshot_restore/public/application/app_providers.tsx b/x-pack/plugins/snapshot_restore/public/application/app_providers.tsx index e2732c0051337..3ca25b7d32dba 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app_providers.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app_providers.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; -import { API_BASE_PATH } from '../../common/constants'; -import { AuthorizationProvider } from './lib/authorization'; +import { API_BASE_PATH } from '../../common'; +import { AuthorizationProvider } from '../shared_imports'; import { AppContextProvider, AppDependencies } from './app_context'; interface Props { @@ -18,10 +18,11 @@ export const AppProviders = ({ appDependencies, children }: Props) => { const { core } = appDependencies; const { i18n: { Context: I18nContext }, + http, } = core; return ( - + {children} diff --git a/x-pack/plugins/snapshot_restore/public/application/components/index.ts b/x-pack/plugins/snapshot_restore/public/application/components/index.ts index a7038ebd71578..f5bb892389870 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/components/index.ts @@ -10,7 +10,6 @@ export { RepositoryDeleteProvider } from './repository_delete_provider'; export { RepositoryForm } from './repository_form'; export { RepositoryVerificationBadge } from './repository_verification_badge'; export { RepositoryTypeLogo } from './repository_type_logo'; -export { SectionError, Error } from './section_error'; export { SectionLoading } from './section_loading'; export { SnapshotDeleteProvider } from './snapshot_delete_provider'; export { RestoreSnapshotForm } from './restore_snapshot_form'; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx index f2d4e2bd74598..105f0601e3dfb 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx @@ -21,13 +21,13 @@ import { } from '@elastic/eui'; import { Repository } from '../../../../../common/types'; -import { CronEditor } from '../../../../shared_imports'; +import { CronEditor, SectionError } from '../../../../shared_imports'; import { useServices } from '../../../app_context'; import { DEFAULT_POLICY_SCHEDULE, DEFAULT_POLICY_FREQUENCY } from '../../../constants'; import { useLoadRepositories } from '../../../services/http'; import { linkToAddRepository } from '../../../services/navigation'; import { documentationLinksService } from '../../../services/documentation'; -import { SectionLoading, SectionError } from '../../'; +import { SectionLoading } from '../../'; import { StepProps } from './'; export const PolicyStepLogistics: React.FunctionComponent = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx index 3b4c9d595b9f2..34bc06343a780 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx @@ -23,13 +23,14 @@ import { } from '@elastic/eui'; import { Repository, RepositoryType, EmptyRepository } from '../../../../common/types'; -import { REPOSITORY_TYPES } from '../../../../common/constants'; +import { REPOSITORY_TYPES } from '../../../../common'; +import { SectionError, Error } from '../../../shared_imports'; import { documentationLinksService } from '../../services/documentation'; import { useLoadRepositoryTypes } from '../../services/http'; import { textService } from '../../services/text'; import { RepositoryValidation } from '../../services/validation'; -import { SectionError, SectionLoading, RepositoryTypeLogo, Error } from '../'; +import { SectionLoading, RepositoryTypeLogo } from '../'; interface Props { repository: Repository | EmptyRepository; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/index.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/index.tsx index 75295a1205cef..1e54868397a6d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/index.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/index.tsx @@ -6,11 +6,11 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { REPOSITORY_TYPES } from '../../../../../common/constants'; +import { REPOSITORY_TYPES } from '../../../../../common'; import { Repository, RepositoryType, EmptyRepository } from '../../../../../common/types'; +import { SectionError } from '../../../../shared_imports'; import { useServices } from '../../../app_context'; import { RepositorySettingsValidation } from '../../../services/validation'; -import { SectionError } from '../../index'; import { AzureSettings } from './azure_settings'; import { FSSettings } from './fs_settings'; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/authorization_provider.tsx deleted file mode 100644 index d32fe29cc1dfa..0000000000000 --- a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/authorization_provider.tsx +++ /dev/null @@ -1,47 +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 React, { createContext } from 'react'; -import { useRequest } from '../../../services/http/use_request'; -import { Privileges } from '../../../../../common/types'; -import { Error } from '../../../components/section_error'; - -interface Authorization { - isLoading: boolean; - apiError: Error | null; - privileges: Privileges; -} - -const initialValue: Authorization = { - isLoading: true, - apiError: null, - privileges: { - hasAllPrivileges: true, - missingPrivileges: {}, - }, -}; - -export const AuthorizationContext = createContext(initialValue); - -interface Props { - privilegesEndpoint: string; - children: React.ReactNode; -} - -export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => { - const { isLoading, error, data: privilegesData } = useRequest({ - path: privilegesEndpoint, - method: 'get', - }); - - const value = { - isLoading, - privileges: isLoading ? { hasAllPrivileges: true, missingPrivileges: {} } : privilegesData, - apiError: error ? error : null, - } as Authorization; - - return {children}; -}; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/index.ts b/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/index.ts deleted file mode 100644 index ac77aa5268660..0000000000000 --- a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/index.ts +++ /dev/null @@ -1,11 +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. - */ - -export { AuthorizationProvider, AuthorizationContext } from './authorization_provider'; - -export { WithPrivileges } from './with_privileges'; - -export { NotAuthorizedSection } from './not_authorized_section'; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/not_authorized_section.tsx b/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/not_authorized_section.tsx deleted file mode 100644 index 3fc13245708e8..0000000000000 --- a/x-pack/plugins/snapshot_restore/public/application/lib/authorization/components/not_authorized_section.tsx +++ /dev/null @@ -1,17 +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 React from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; - -interface Props { - title: React.ReactNode; - message: React.ReactNode | string; -} - -export const NotAuthorizedSection = ({ title, message }: Props) => ( - {title}} body={

{message}

} /> -); diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx index f3110199ee17c..03d381c9f3aa3 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx @@ -26,6 +26,7 @@ import { import { SlmPolicy } from '../../../../../../common/types'; import { useServices } from '../../../../app_context'; +import { SectionError, Error } from '../../../../../shared_imports'; import { UIM_POLICY_DETAIL_PANEL_SUMMARY_TAB, UIM_POLICY_DETAIL_PANEL_HISTORY_TAB, @@ -34,11 +35,9 @@ import { useLoadPolicy } from '../../../../services/http'; import { linkToEditPolicy, linkToSnapshot } from '../../../../services/navigation'; import { - SectionError, SectionLoading, PolicyExecuteProvider, PolicyDeleteProvider, - Error, } from '../../../../components'; import { TabSummary, TabHistory } from './tabs'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index 0122e25e5e165..51297038b0f3f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -9,13 +9,19 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { + SectionError, + Error, + WithPrivileges, + NotAuthorizedSection, +} from '../../../../shared_imports'; + import { SlmPolicy } from '../../../../../common/types'; -import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common/constants'; -import { SectionError, SectionLoading, Error } from '../../../components'; +import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common'; +import { SectionLoading } from '../../../components'; import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants'; import { useLoadPolicies, useLoadRetentionSettings } from '../../../services/http'; import { linkToAddPolicy, linkToPolicy } from '../../../services/navigation'; -import { WithPrivileges, NotAuthorizedSection } from '../../../lib/authorization'; import { useServices } from '../../../app_context'; import { PolicyDetails } from './policy_details'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_table/policy_table.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_table/policy_table.tsx index 7f9c5c5af7705..ba28bcddf5347 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_table/policy_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_table/policy_table.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import { SlmPolicy } from '../../../../../../common/types'; +import { Error } from '../../../../../shared_imports'; import { UIM_POLICY_SHOW_DETAILS_CLICK } from '../../../../constants'; import { useServices } from '../../../../app_context'; import { @@ -28,7 +29,6 @@ import { PolicyExecuteProvider, PolicyDeleteProvider, } from '../../../../components'; -import { Error } from '../../../../components/section_error'; import { linkToAddPolicy, linkToEditPolicy } from '../../../../services/navigation'; import { SendRequestResponse } from '../../../../../shared_imports'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx index d293f194f647a..9932f14664076 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx @@ -25,6 +25,8 @@ import { import 'brace/theme/textmate'; +import { SectionError, Error } from '../../../../../shared_imports'; + import { useServices } from '../../../../app_context'; import { documentationLinksService } from '../../../../services/documentation'; import { @@ -35,7 +37,8 @@ import { import { textService } from '../../../../services/text'; import { linkToSnapshots, linkToEditRepository } from '../../../../services/navigation'; -import { REPOSITORY_TYPES } from '../../../../../../common/constants'; +import { REPOSITORY_TYPES } from '../../../../../../common'; + import { Repository, RepositoryVerification, @@ -43,10 +46,8 @@ import { } from '../../../../../../common/types'; import { RepositoryDeleteProvider, - SectionError, SectionLoading, RepositoryVerificationBadge, - Error, } from '../../../../components'; import { TypeDetails } from './type_details'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx index 6fa12537e9d6f..2256fa5991dec 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx @@ -10,7 +10,8 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { Repository } from '../../../../../common/types'; -import { SectionError, SectionLoading, Error } from '../../../components'; +import { SectionError, Error } from '../../../../shared_imports'; +import { SectionLoading } from '../../../components'; import { BASE_PATH, UIM_REPOSITORY_LIST_LOAD } from '../../../constants'; import { useServices } from '../../../app_context'; import { useLoadRepositories } from '../../../services/http'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx index 7c0438f6b837f..bf2643d78bca1 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx @@ -15,15 +15,14 @@ import { EuiIconTip, } from '@elastic/eui'; -import { REPOSITORY_TYPES } from '../../../../../../common/constants'; +import { REPOSITORY_TYPES } from '../../../../../../common'; import { Repository, RepositoryType } from '../../../../../../common/types'; -import { Error } from '../../../../components/section_error'; +import { Error, SendRequestResponse } from '../../../../../shared_imports'; import { RepositoryDeleteProvider } from '../../../../components'; import { UIM_REPOSITORY_SHOW_DETAILS_CLICK } from '../../../../constants'; import { useServices } from '../../../../app_context'; import { textService } from '../../../../services/text'; import { linkToEditRepository, linkToAddRepository } from '../../../../services/navigation'; -import { SendRequestResponse } from '../../../../../shared_imports'; interface Props { repositories: Repository[]; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx index da9ce3b124a11..0e3d9363d0535 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx @@ -18,14 +18,19 @@ import { EuiLoadingSpinner, EuiLink, } from '@elastic/eui'; -import { APP_RESTORE_INDEX_PRIVILEGES } from '../../../../../common/constants'; -import { SectionError, SectionLoading, Error } from '../../../components'; +import { APP_RESTORE_INDEX_PRIVILEGES } from '../../../../../common'; +import { + WithPrivileges, + NotAuthorizedSection, + SectionError, + Error, +} from '../../../../shared_imports'; +import { SectionLoading } from '../../../components'; import { UIM_RESTORE_LIST_LOAD } from '../../../constants'; import { useLoadRestores } from '../../../services/http'; import { linkToSnapshots } from '../../../services/navigation'; import { useServices } from '../../../app_context'; import { RestoreTable } from './restore_table'; -import { WithPrivileges, NotAuthorizedSection } from '../../../lib/authorization'; const ONE_SECOND_MS = 1000; const TEN_SECONDS_MS = 10 * 1000; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index d16545debe1ec..1943762a3c36e 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -23,12 +23,8 @@ import React, { Fragment, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { SnapshotDetails as ISnapshotDetails } from '../../../../../../common/types'; -import { - SectionError, - SectionLoading, - SnapshotDeleteProvider, - Error, -} from '../../../../components'; +import { SectionError, Error } from '../../../../../shared_imports'; +import { SectionLoading, SnapshotDeleteProvider } from '../../../../components'; import { useServices } from '../../../../app_context'; import { UIM_SNAPSHOT_DETAIL_PANEL_SUMMARY_TAB, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx index fe99ccb6f596c..30e4c771644bc 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx @@ -10,10 +10,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiButton, EuiCallOut, EuiLink, EuiEmptyPrompt, EuiSpacer, EuiIcon } from '@elastic/eui'; -import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common/constants'; -import { SectionError, SectionLoading, Error } from '../../../components'; +import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common'; +import { WithPrivileges, SectionError, Error } from '../../../../shared_imports'; +import { SectionLoading } from '../../../components'; import { BASE_PATH, UIM_SNAPSHOT_LIST_LOAD } from '../../../constants'; -import { WithPrivileges } from '../../../lib/authorization'; import { documentationLinksService } from '../../../services/documentation'; import { useLoadSnapshots } from '../../../services/http'; import { diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx index ad64dcc7adcfe..427c241970007 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx @@ -17,10 +17,10 @@ import { } from '@elastic/eui'; import { SnapshotDetails } from '../../../../../../common/types'; +import { Error } from '../../../../../shared_imports'; import { SNAPSHOT_STATE, UIM_SNAPSHOT_SHOW_DETAILS_CLICK } from '../../../../constants'; import { useServices } from '../../../../app_context'; import { linkToRepository, linkToRestoreSnapshot } from '../../../../services/navigation'; -import { Error } from '../../../../components/section_error'; import { DataPlaceholder, FormattedDateTime, SnapshotDeleteProvider } from '../../../../components'; import { SendRequestResponse } from '../../../../../shared_imports'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx index 4eb0f54978d09..6d1a432be7f9f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx @@ -9,9 +9,11 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../common/types'; -import { TIME_UNITS } from '../../../../common/constants'; +import { TIME_UNITS } from '../../../../common'; -import { PolicyForm, SectionError, SectionLoading, Error } from '../../components'; +import { SectionError, Error } from '../../../shared_imports'; + +import { PolicyForm, SectionLoading } from '../../components'; import { BASE_PATH, DEFAULT_POLICY_SCHEDULE } from '../../constants'; import { breadcrumbService, docTitleService } from '../../services/navigation'; import { addPolicy, useLoadIndices } from '../../services/http'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx index 9ca7eba5c4eeb..0f1473fc05492 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx @@ -9,8 +9,9 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../common/types'; +import { SectionError, Error } from '../../../shared_imports'; import { TIME_UNITS } from '../../../../common/constants'; -import { SectionError, SectionLoading, PolicyForm, Error } from '../../components'; +import { SectionLoading, PolicyForm } from '../../components'; import { BASE_PATH } from '../../constants'; import { useServices } from '../../app_context'; import { breadcrumbService, docTitleService } from '../../services/navigation'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx index 126e04bc7dc1d..08bfde833c368 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx @@ -12,7 +12,9 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { Repository, EmptyRepository } from '../../../../common/types'; -import { RepositoryForm, SectionError } from '../../components'; +import { SectionError } from '../../../shared_imports'; + +import { RepositoryForm } from '../../components'; import { BASE_PATH, Section } from '../../constants'; import { breadcrumbService, docTitleService } from '../../services/navigation'; import { addRepository } from '../../services/http'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx index aa29b8b9f0551..95f8b9b8bde7d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx @@ -10,7 +10,8 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiCallOut, EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { Repository, EmptyRepository } from '../../../../common/types'; -import { RepositoryForm, SectionError, SectionLoading, Error } from '../../components'; +import { SectionError, Error } from '../../../shared_imports'; +import { RepositoryForm, SectionLoading } from '../../components'; import { BASE_PATH, Section } from '../../constants'; import { useServices } from '../../app_context'; import { breadcrumbService, docTitleService } from '../../services/navigation'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx index 252fd07a85f80..9eabed8341ee0 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx @@ -9,8 +9,9 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { SnapshotDetails, RestoreSettings } from '../../../../common/types'; +import { SectionError, Error } from '../../../shared_imports'; import { BASE_PATH } from '../../constants'; -import { SectionError, SectionLoading, RestoreSnapshotForm, Error } from '../../components'; +import { SectionLoading, RestoreSnapshotForm } from '../../components'; import { useServices } from '../../app_context'; import { breadcrumbService, docTitleService } from '../../services/navigation'; import { useLoadSnapshot, executeRestore } from '../../services/http'; diff --git a/x-pack/plugins/snapshot_restore/public/application/services/http/use_request.ts b/x-pack/plugins/snapshot_restore/public/application/services/http/use_request.ts index 200d601fd2ce9..27a565ccb74bc 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/http/use_request.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/http/use_request.ts @@ -9,11 +9,10 @@ import { UseRequestConfig, sendRequest as _sendRequest, useRequest as _useRequest, + Error as CustomError, } from '../../../shared_imports'; -import { Error as CustomError } from '../../components/section_error'; - -import { httpService } from './index'; +import { httpService } from '.'; export const sendRequest = (config: SendRequestConfig) => { return _sendRequest(httpService.httpClient, config); diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts index 7e7ef09d0c09d..e0024ea8e0c12 100644 --- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts +++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts @@ -12,4 +12,10 @@ export { useRequest, CronEditor, DAY, + SectionError, + Error, + WithPrivileges, + useAuthorizationContext, + NotAuthorizedSection, + AuthorizationProvider, } from '../../../../src/plugins/es_ui_shared/public'; diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/app.ts b/x-pack/plugins/snapshot_restore/server/routes/api/app.ts index 5d334fddc144b..bda64fdb66571 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/app.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/app.ts @@ -3,12 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Privileges } from '../../../common/types'; +import { Privileges } from '../../../../../../src/plugins/es_ui_shared/public'; + import { APP_REQUIRED_CLUSTER_PRIVILEGES, APP_RESTORE_INDEX_PRIVILEGES, APP_SLM_CLUSTER_PRIVILEGES, -} from '../../../common/constants'; +} from '../../../common'; import { RouteDependencies } from '../../types'; import { addBasePath } from '../helpers'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 209a3f626272f..8a606e230dc36 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8787,8 +8787,6 @@ "xpack.logstash.idFormatErrorMessage": "パイプライン ID は文字またはアンダーラインで始まる必要があり、文字、アンダーライン、ハイフン、数字のみ使用できます", "xpack.logstash.insufficientUserPermissionsDescription": "Logstash パイプラインの管理に必要なユーザーパーミッションがありません", "xpack.logstash.kibanaManagementPipelinesTitle": "Kibana の管理で作成されたパイプラインだけがここに表示されます", - "xpack.logstash.managementSection.createPipelineTitle": "パイプラインの作成", - "xpack.logstash.managementSection.editPipelineTitle": "パイプラインの編集", "xpack.logstash.managementSection.enableSecurityDescription": "Logstash パイプライン管理機能を使用するには、セキュリティを有効にする必要があります。elasticsearch.yml で xpack.security.enabled: true に設定してください。", "xpack.logstash.managementSection.licenseDoesNotSupportDescription": "ご使用の {licenseType} ライセンスは Logstash パイプライン管理をサポートしていません。ライセンスをアップグレードしてください。", "xpack.logstash.managementSection.notPossibleToManagePipelinesMessage": "現在ライセンス情報が利用できないため Logstash パイプラインを使用できません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5d8d733f2b5b6..faee95b8172b7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8790,8 +8790,6 @@ "xpack.logstash.idFormatErrorMessage": "管道 ID 必须以字母或下划线开头,并只能包含字母、下划线、短划线和数字", "xpack.logstash.insufficientUserPermissionsDescription": "管理 Logstash 管道的用户权限不足", "xpack.logstash.kibanaManagementPipelinesTitle": "仅在 Kibana“管理”中创建的管道显示在此处", - "xpack.logstash.managementSection.createPipelineTitle": "创建管道", - "xpack.logstash.managementSection.editPipelineTitle": "编辑管道", "xpack.logstash.managementSection.enableSecurityDescription": "必须启用 Security,才能使用 Logstash 管道管理功能。请在 elasticsearch.yml 中设置 xpack.security.enabled: true。", "xpack.logstash.managementSection.licenseDoesNotSupportDescription": "您的{licenseType}许可不支持 Logstash 管道管理功能。请升级您的许可。", "xpack.logstash.managementSection.notPossibleToManagePipelinesMessage": "您不能管理 Logstash 管道,因为许可信息当前不可用。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx index 7da97b9fe3436..1c9e87310107f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx @@ -90,7 +90,7 @@ describe('pagerduty action params validation', () => { summary: '2323', source: 'source', severity: 'critical', - timestamp: '234654564654', + timestamp: new Date().toISOString(), component: 'test', group: 'group', class: 'test class', @@ -99,6 +99,7 @@ describe('pagerduty action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { summary: [], + timestamp: [], }, }); }); @@ -156,7 +157,7 @@ describe('PagerDutyParamsFields renders', () => { summary: '2323', source: 'source', severity: SeverityActionOptions.CRITICAL, - timestamp: '234654564654', + timestamp: new Date().toISOString(), component: 'test', group: 'group', class: 'test class', @@ -164,7 +165,7 @@ describe('PagerDutyParamsFields renders', () => { const wrapper = mountWithIntl( {}} index={0} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx index d99362c618356..15f91ae1d4609 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import moment from 'moment'; import { ActionTypeModel, ActionConnectorFieldsProps, @@ -23,6 +24,7 @@ import { import { PagerDutyActionParams, PagerDutyActionConnector } from './types'; import pagerDutySvg from './pagerduty.svg'; import { AddMessageVariables } from '../add_message_variables'; +import { hasMustacheTokens } from '../../lib/has_mustache_tokens'; export function getActionType(): ActionTypeModel { return { @@ -62,6 +64,7 @@ export function getActionType(): ActionTypeModel { const validationResult = { errors: {} }; const errors = { summary: new Array(), + timestamp: new Array(), }; validationResult.errors = errors; if (!actionParams.summary?.length) { @@ -74,6 +77,24 @@ export function getActionType(): ActionTypeModel { ) ); } + if (actionParams.timestamp && !hasMustacheTokens(actionParams.timestamp)) { + if (isNaN(Date.parse(actionParams.timestamp))) { + const { nowShortFormat, nowLongFormat } = getValidTimestampExamples(); + errors.timestamp.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestamp', + { + defaultMessage: + 'Timestamp must be a valid date, such as {nowShortFormat} or {nowLongFormat}.', + values: { + nowShortFormat, + nowLongFormat, + }, + } + ) + ); + } + } return validationResult; }, actionConnectorFields: PagerDutyActionConnectorFields, @@ -334,6 +355,8 @@ const PagerDutyParamsFields: React.FunctionComponent 0 && timestamp !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.timestampTextFieldLabel', { @@ -355,11 +378,14 @@ const PagerDutyParamsFields: React.FunctionComponent 0 && timestamp !== undefined} onChange={(e: React.ChangeEvent) => { editAction('timestamp', e.target.value, index); }} onBlur={() => { - if (!timestamp) { + if (timestamp?.trim()) { + editAction('timestamp', timestamp.trim(), index); + } else { editAction('timestamp', '', index); } }} @@ -534,3 +560,11 @@ const PagerDutyParamsFields: React.FunctionComponent ); }; + +function getValidTimestampExamples() { + const now = moment(); + return { + nowShortFormat: now.format('YYYY-MM-DD'), + nowLongFormat: now.format('YYYY-MM-DD h:mm:ss'), + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.test.ts new file mode 100644 index 0000000000000..db4f9fa799170 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.test.ts @@ -0,0 +1,23 @@ +/* + * 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 uuid from 'uuid'; + +import { hasMustacheTokens } from './has_mustache_tokens'; + +describe('hasMustacheTokens', () => { + test('returns false for empty string', () => { + expect(hasMustacheTokens('')).toBe(false); + }); + + test('returns false for string without tokens', () => { + expect(hasMustacheTokens(`some random string ${uuid.v4()}`)).toBe(false); + }); + + test('returns true when a template token is present', () => { + expect(hasMustacheTokens('{{context.timestamp}}')).toBe(true); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.ts new file mode 100644 index 0000000000000..4dcd8113d51fc --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/has_mustache_tokens.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export function hasMustacheTokens(str: string): boolean { + return null !== str.match(/{{.*}}/); +} diff --git a/x-pack/test/api_integration/apis/logstash/pipeline/delete.ts b/x-pack/test/api_integration/apis/logstash/pipeline/delete.ts index cdbf5a3e6a1fe..2463dbe4500b5 100644 --- a/x-pack/test/api_integration/apis/logstash/pipeline/delete.ts +++ b/x-pack/test/api_integration/apis/logstash/pipeline/delete.ts @@ -20,7 +20,6 @@ export default function({ getService }: FtrProviderContext) { .send({ id: 'fast_generator', description: 'foobar baz', - username: 'seger', pipeline: 'input { generator {} }\n\n output { stdout {} }', }) .expect(204); diff --git a/x-pack/test/api_integration/apis/logstash/pipeline/save.ts b/x-pack/test/api_integration/apis/logstash/pipeline/save.ts index 2ca9fbe7d68e0..ca0cfb19b9454 100644 --- a/x-pack/test/api_integration/apis/logstash/pipeline/save.ts +++ b/x-pack/test/api_integration/apis/logstash/pipeline/save.ts @@ -28,7 +28,6 @@ export default function({ getService }: FtrProviderContext) { .send({ id: 'fast_generator', description: 'foobar baz', - username: 'seger', pipeline: 'input { generator {} }\n\n output { stdout {} }', }) .expect(204);