From 2744f704665646c24e949032431a571e274c8c1e Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 21 Apr 2021 20:58:39 +0200 Subject: [PATCH 01/33] Remove the no longer used release-notes script (#97806) * Remove the no longer used release-notes script * Commit missing file --- .eslintignore | 1 - package.json | 24 +- packages/kbn-docs-utils/src/index.ts | 1 - .../kbn-docs-utils/src/release_notes/cli.ts | 152 ---------- .../src/release_notes/formats/asciidoc.ts | 73 ----- .../src/release_notes/formats/csv.ts | 63 ---- .../src/release_notes/formats/format.ts | 23 -- .../src/release_notes/formats/index.ts | 14 - .../kbn-docs-utils/src/release_notes/index.ts | 9 - .../src/release_notes/lib/classify_pr.ts | 55 ---- .../lib/get_fix_references.test.ts | 57 ---- .../release_notes/lib/get_fix_references.ts | 18 -- .../lib/get_note_from_description.test.ts | 72 ----- .../lib/get_note_from_description.ts | 25 -- .../src/release_notes/lib/index.ts | 15 - .../lib/irrelevant_pr_summary.ts | 50 ---- .../src/release_notes/lib/is_pr_relevant.ts | 50 ---- .../src/release_notes/lib/pr_api.ts | 222 -------------- .../src/release_notes/lib/streams.ts | 23 -- .../src/release_notes/lib/type_helpers.ts | 9 - .../src/release_notes/lib/version.test.ts | 135 --------- .../src/release_notes/lib/version.ts | 112 ------- .../src/release_notes/release_notes_config.ts | 283 ------------------ scripts/release_notes.js | 10 - yarn.lock | 22 -- 25 files changed, 10 insertions(+), 1508 deletions(-) delete mode 100644 packages/kbn-docs-utils/src/release_notes/cli.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/csv.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/format.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/index.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/index.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/index.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/streams.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/version.test.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/version.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/release_notes_config.ts delete mode 100644 scripts/release_notes.js diff --git a/.eslintignore b/.eslintignore index 4058d971b7642..ce21d5bb31264 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,4 @@ **/*.js.snap -**/graphql/types.ts /.es /.chromium /build diff --git a/package.json b/package.json index 047dd38d92cd8..0ed0d3bbc3b7f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "**/deepmerge": "^4.2.2", "**/fast-deep-equal": "^3.1.1", "globby/fast-glob": "3.2.5", - "**/graphql-toolkit/lodash": "^4.17.21", "**/hoist-non-react-statics": "^3.3.2", "**/isomorphic-fetch/node-fetch": "^2.6.1", "**/istanbul-instrumenter-loader/schema-utils": "1.0.0", @@ -193,10 +192,10 @@ "compare-versions": "3.5.1", "concat-stream": "1.6.2", "constate": "^1.3.2", - "cronstrue": "^1.51.0", "content-disposition": "0.5.3", "copy-to-clipboard": "^3.0.8", "core-js": "^3.6.5", + "cronstrue": "^1.51.0", "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", "d3": "3.5.17", @@ -231,8 +230,6 @@ "glob": "^7.1.2", "glob-all": "^3.2.1", "globby": "^11.0.3", - "graphql": "^0.13.2", - "graphql-tag": "^2.10.3", "handlebars": "4.7.7", "he": "^1.2.0", "history": "^4.9.0", @@ -274,9 +271,9 @@ "lodash": "^4.17.21", "lru-cache": "^4.1.5", "lz-string": "^1.4.4", - "markdown-it": "^10.0.0", "mapbox-gl": "1.13.1", "mapbox-gl-draw-rectangle-mode": "^1.0.4", + "markdown-it": "^10.0.0", "md5": "^2.1.0", "memoize-one": "^5.0.0", "mime": "^2.4.4", @@ -298,12 +295,12 @@ "object-path-immutable": "^3.1.1", "opn": "^5.5.0", "oppsy": "^2.0.0", + "p-limit": "^3.0.1", "p-map": "^4.0.0", "p-retry": "^4.2.0", "papaparse": "^5.2.0", "pdfmake": "^0.1.65", "pegjs": "0.10.0", - "p-limit": "^3.0.1", "pluralize": "3.1.0", "pngjs": "^3.4.0", "polished": "^1.9.2", @@ -335,19 +332,19 @@ "react-monaco-editor": "^0.41.2", "react-popper-tooltip": "^2.10.1", "react-query": "^3.13.10", + "react-redux": "^7.2.0", + "react-resizable": "^1.7.5", "react-resize-detector": "^4.2.0", "react-reverse-portal": "^1.0.4", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-router-redux": "^4.0.8", "react-shortcuts": "^2.0.0", "react-sizeme": "^2.3.6", "react-syntax-highlighter": "^15.3.1", - "react-redux": "^7.2.0", - "react-resizable": "^1.7.5", - "react-router": "^5.2.0", - "react-router-dom": "^5.2.0", "react-tiny-virtual-list": "^2.2.0", - "react-virtualized": "^9.21.2", "react-use": "^15.3.8", + "react-virtualized": "^9.21.2", "react-vis": "^1.8.1", "react-visibility-sensor": "^5.1.1", "reactcss": "1.2.3", @@ -376,8 +373,8 @@ "strip-ansi": "^6.0.0", "style-it": "^2.1.3", "styled-components": "^5.1.0", - "symbol-observable": "^1.2.0", "suricata-sid-db": "^1.0.2", + "symbol-observable": "^1.2.0", "tabbable": "1.1.3", "tar": "4.4.13", "tinycolor2": "1.4.1", @@ -521,7 +518,6 @@ "@types/getos": "^3.0.0", "@types/git-url-parse": "^9.0.0", "@types/glob": "^7.1.2", - "@types/graphql": "^0.13.2", "@types/gulp": "^4.0.6", "@types/gulp-zip": "^4.0.1", "@types/hapi__cookie": "^10.1.1", @@ -735,8 +731,8 @@ "jest-circus": "^26.6.3", "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", - "jest-environment-jsdom-thirteen": "^1.0.1", "jest-environment-jsdom": "^26.6.2", + "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", "jest-silent-reporter": "^0.2.1", "jest-snapshot": "^26.6.2", diff --git a/packages/kbn-docs-utils/src/index.ts b/packages/kbn-docs-utils/src/index.ts index 24aef1bf891f6..5accd1fa2984f 100644 --- a/packages/kbn-docs-utils/src/index.ts +++ b/packages/kbn-docs-utils/src/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export * from './release_notes'; export * from './api_docs'; diff --git a/packages/kbn-docs-utils/src/release_notes/cli.ts b/packages/kbn-docs-utils/src/release_notes/cli.ts deleted file mode 100644 index e6d1c717459b1..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/cli.ts +++ /dev/null @@ -1,152 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Fs from 'fs'; -import Path from 'path'; -import { inspect } from 'util'; - -import { REPO_ROOT } from '@kbn/utils'; -import { run, createFlagError, createFailError } from '@kbn/dev-utils'; - -import { FORMATS, SomeFormat } from './formats'; -import { - PrApi, - Version, - ClassifiedPr, - streamFromIterable, - asyncPipeline, - IrrelevantPrSummary, - isPrRelevant, - classifyPr, -} from './lib'; - -const rootPackageJson = JSON.parse( - Fs.readFileSync(Path.resolve(REPO_ROOT, 'package.json'), 'utf8') -); -const extensions = FORMATS.map((f) => f.extension); - -export function runReleaseNotesCli() { - run( - async ({ flags, log }) => { - const token = flags.token; - if (!token || typeof token !== 'string') { - throw createFlagError('--token must be defined'); - } - const prApi = new PrApi(log, token); - - const version = Version.fromFlag(flags.version); - if (!version) { - throw createFlagError('unable to parse --version, use format "v{major}.{minor}.{patch}"'); - } - - const includeVersions = Version.fromFlags(flags.include || []); - if (!includeVersions) { - throw createFlagError('unable to parse --include, use format "v{major}.{minor}.{patch}"'); - } - - const Formats: SomeFormat[] = []; - for (const flag of Array.isArray(flags.format) ? flags.format : [flags.format]) { - const Format = FORMATS.find((F) => F.extension === flag); - if (!Format) { - throw createFlagError(`--format must be one of "${extensions.join('", "')}"`); - } - Formats.push(Format); - } - - const filename = flags.filename; - if (!filename || typeof filename !== 'string') { - throw createFlagError('--filename must be a string'); - } - - if (flags['debug-pr']) { - const number = parseInt(String(flags['debug-pr']), 10); - if (Number.isNaN(number)) { - throw createFlagError('--debug-pr must be a pr number when specified'); - } - - const summary = new IrrelevantPrSummary(log); - const pr = await prApi.getPr(number); - log.success( - inspect( - { - version: version.label, - includeVersions: includeVersions.map((v) => v.label), - isPrRelevant: isPrRelevant(pr, version, includeVersions, summary), - ...classifyPr(pr, log), - pr, - }, - { depth: 100 } - ) - ); - summary.logStats(); - return; - } - - log.info(`Loading all PRs with label [${version.label}] to build release notes...`); - - const summary = new IrrelevantPrSummary(log); - const prsToReport: ClassifiedPr[] = []; - const prIterable = prApi.iterRelevantPullRequests(version); - for await (const pr of prIterable) { - if (!isPrRelevant(pr, version, includeVersions, summary)) { - continue; - } - prsToReport.push(classifyPr(pr, log)); - } - summary.logStats(); - - if (!prsToReport.length) { - throw createFailError( - `All PRs with label [${version.label}] were filtered out by the config. Run again with --debug for more info.` - ); - } - - log.info(`Found ${prsToReport.length} prs to report on`); - - for (const Format of Formats) { - const format = new Format(version, prsToReport, log); - const outputPath = Path.resolve(`${filename}.${Format.extension}`); - await asyncPipeline(streamFromIterable(format.print()), Fs.createWriteStream(outputPath)); - log.success(`[${Format.extension}] report written to ${outputPath}`); - } - }, - { - usage: `node scripts/release_notes --token {token} --version {version}`, - flags: { - alias: { - version: 'v', - include: 'i', - }, - string: ['token', 'version', 'format', 'filename', 'include', 'debug-pr'], - default: { - filename: 'report', - version: rootPackageJson.version, - format: extensions, - }, - help: ` - --token (required) The Github access token to use for requests - --version, -v The version to fetch PRs by, PRs with version labels prior to - this one will be ignored (see --include-version) (default ${ - rootPackageJson.version - }) - --include, -i A version that is before --version but shouldn't be considered - "released" and cause PRs with a matching label to be excluded from - release notes. Use this when PRs are labeled with a version that - is less that --version and is expected to be released after - --version, can be specified multiple times. - --format Only produce a certain format, options: "${extensions.join('", "')}" - --filename Output filename, defaults to "report" - --debug-pr Fetch and print the details for a single PR, disabling reporting - `, - }, - description: ` - Fetch details from Github PRs for generating release notes - `, - } - ); -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts b/packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts deleted file mode 100644 index df86f6c7a40e1..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts +++ /dev/null @@ -1,73 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import dedent from 'dedent'; - -import { Format } from './format'; -import { - ASCIIDOC_SECTIONS, - UNKNOWN_ASCIIDOC_SECTION, - AREAS, - UNKNOWN_AREA, -} from '../release_notes_config'; - -function* lines(body: string) { - for (const line of dedent(body).split('\n')) { - yield `${line}\n`; - } -} - -export class AsciidocFormat extends Format { - static extension = 'asciidoc'; - - *print() { - const sortedAreas = [ - ...AREAS.slice().sort((a, b) => a.title.localeCompare(b.title)), - UNKNOWN_AREA, - ]; - - yield* lines(` - [[release-notes-${this.version.label}]] - == ${this.version.label} Release Notes - - Also see <>. - `); - - for (const section of [...ASCIIDOC_SECTIONS, UNKNOWN_ASCIIDOC_SECTION]) { - const prsInSection = this.prs.filter((pr) => pr.asciidocSection === section); - if (!prsInSection.length) { - continue; - } - - yield '\n'; - yield* lines(` - [float] - [[${section.id}-${this.version.label}]] - === ${section.title} - `); - - for (const area of sortedAreas) { - const prsInArea = prsInSection.filter((pr) => pr.area === area); - - if (!prsInArea.length) { - continue; - } - - yield `${area.title}::\n`; - for (const pr of prsInArea) { - const fixes = pr.fixes.length ? `[Fixes ${pr.fixes.join(', ')}] ` : ''; - const strippedTitle = pr.title.replace(/^\s*\[[^\]]+\]\s*/, ''); - yield `* ${fixes}${strippedTitle} {kibana-pull}${pr.number}[#${pr.number}]\n`; - if (pr.note) { - yield ` - ${pr.note}\n`; - } - } - } - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/csv.ts b/packages/kbn-docs-utils/src/release_notes/formats/csv.ts deleted file mode 100644 index ad03ebaff8049..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/csv.ts +++ /dev/null @@ -1,63 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Format } from './format'; - -/** - * Escape a value to conform to field and header encoding defined at https://tools.ietf.org/html/rfc4180 - */ -function esc(value: string | number) { - if (typeof value === 'number') { - return String(value); - } - - if (!value.includes(',') && !value.includes('\n') && !value.includes('"')) { - return value; - } - - return `"${value.split('"').join('""')}"`; -} - -function row(...fields: Array) { - return fields.map(esc).join(',') + '\r\n'; -} - -export class CsvFormat extends Format { - static extension = 'csv'; - - *print() { - // columns - yield row( - 'areas', - 'versions', - 'user', - 'title', - 'number', - 'url', - 'date', - 'fixes', - 'labels', - 'state' - ); - - for (const pr of this.prs) { - yield row( - pr.area.title, - pr.versions.map((v) => v.label).join(', '), - pr.user.name || pr.user.login, - pr.title, - pr.number, - pr.url, - pr.mergedAt, - pr.fixes.join(', '), - pr.labels.join(', '), - pr.state - ); - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/format.ts b/packages/kbn-docs-utils/src/release_notes/formats/format.ts deleted file mode 100644 index 937beb2f3fd67..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/format.ts +++ /dev/null @@ -1,23 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ToolingLog } from '@kbn/dev-utils'; - -import { Version, ClassifiedPr } from '../lib'; - -export abstract class Format { - static extension: string; - - constructor( - protected readonly version: Version, - protected readonly prs: ClassifiedPr[], - protected readonly log: ToolingLog - ) {} - - abstract print(): Iterator; -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/index.ts b/packages/kbn-docs-utils/src/release_notes/formats/index.ts deleted file mode 100644 index 2019dce53f537..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/index.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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ArrayItem } from '../lib'; -import { AsciidocFormat } from './asciidoc'; -import { CsvFormat } from './csv'; - -export const FORMATS = [CsvFormat, AsciidocFormat] as const; -export type SomeFormat = ArrayItem; diff --git a/packages/kbn-docs-utils/src/release_notes/index.ts b/packages/kbn-docs-utils/src/release_notes/index.ts deleted file mode 100644 index 7ee97ec9aa05d..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './cli'; diff --git a/packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts b/packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts deleted file mode 100644 index ca24367fa7288..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts +++ /dev/null @@ -1,55 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ToolingLog } from '@kbn/dev-utils'; - -import { - Area, - AREAS, - UNKNOWN_AREA, - AsciidocSection, - ASCIIDOC_SECTIONS, - UNKNOWN_ASCIIDOC_SECTION, -} from '../release_notes_config'; -import { PullRequest } from './pr_api'; - -export interface ClassifiedPr extends PullRequest { - area: Area; - asciidocSection: AsciidocSection; -} - -export function classifyPr(pr: PullRequest, log: ToolingLog): ClassifiedPr { - const filter = (a: Area | AsciidocSection) => - a.labels.some((test) => - typeof test === 'string' ? pr.labels.includes(test) : pr.labels.some((l) => l.match(test)) - ); - - const areas = AREAS.filter(filter); - const asciidocSections = ASCIIDOC_SECTIONS.filter(filter); - - const pickOne = (name: string, options: T[]) => { - if (options.length > 1) { - const matches = options.map((o) => o.title).join(', '); - log.warning(`[${pr.terminalLink}] ambiguous ${name}, mulitple match [${matches}]`); - return options[0]; - } - - if (options.length === 0) { - log.error(`[${pr.terminalLink}] unable to determine ${name} because none match`); - return; - } - - return options[0]; - }; - - return { - ...pr, - area: pickOne('area', areas) || UNKNOWN_AREA, - asciidocSection: pickOne('asciidoc section', asciidocSections) || UNKNOWN_ASCIIDOC_SECTION, - }; -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts deleted file mode 100644 index 8cc8aec19f94e..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts +++ /dev/null @@ -1,57 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getFixReferences } from './get_fix_references'; - -it('returns all fixed issue mentions in the PR text', () => { - expect( - getFixReferences(` - clOses #1 - closes: #2 - clOse #3 - close: #4 - clOsed #5 - closed: #6 - fiX #7 - fix: #8 - fiXes #9 - fixes: #10 - fiXed #11 - fixed: #12 - reSolve #13 - resolve: #14 - reSolves #15 - resolves: #16 - reSolved #17 - resolved: #18 - fixed - #19 - `) - ).toMatchInlineSnapshot(` - Array [ - "#1", - "#2", - "#3", - "#4", - "#5", - "#6", - "#7", - "#8", - "#9", - "#10", - "#11", - "#12", - "#13", - "#14", - "#15", - "#16", - "#17", - "#18", - ] - `); -}); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts deleted file mode 100644 index c4c8ed0f9a9ea..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const FIXES_RE = /(?:closes|close|closed|fix|fixes|fixed|resolve|resolves|resolved)[ :]*(#\d*)/gi; - -export function getFixReferences(prText: string) { - const fixes: string[] = []; - let match; - while ((match = FIXES_RE.exec(prText))) { - fixes.push(match[1]); - } - return fixes; -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts deleted file mode 100644 index 59945a835a3c9..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts +++ /dev/null @@ -1,72 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import MarkdownIt from 'markdown-it'; -import dedent from 'dedent'; - -import { getNoteFromDescription } from './get_note_from_description'; - -it('extracts expected components from html', () => { - const mk = new MarkdownIt(); - - expect( - getNoteFromDescription( - mk.render(dedent` - My PR description - - Fixes: #1234 - - ## Release Note: - - Checkout this feature - `), - 'release note' - ) - ).toMatchInlineSnapshot(`"Checkout this feature"`); - - expect( - getNoteFromDescription( - mk.render(dedent` - My PR description - - Fixes: #1234 - - #### Dev docs: - - We fixed an issue - `), - 'dev docs' - ) - ).toMatchInlineSnapshot(`"We fixed an issue"`); - - expect( - getNoteFromDescription( - mk.render(dedent` - My PR description - - Fixes: #1234 - - OTHER TITLE: Checkout feature foo - `), - 'other title' - ) - ).toMatchInlineSnapshot(`"Checkout feature foo"`); - - expect( - getNoteFromDescription( - mk.render(dedent` - # Summary - - My PR description - - release note : bar - `), - 'release note' - ) - ).toMatchInlineSnapshot(`"bar"`); -}); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts deleted file mode 100644 index db80c29454cf4..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts +++ /dev/null @@ -1,25 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import cheerio from 'cheerio'; - -export function getNoteFromDescription(descriptionHtml: string, header: string) { - const re = new RegExp(`^(\\s*${header.toLowerCase()}(?:s)?\\s*:?\\s*)`, 'i'); - const $ = cheerio.load(descriptionHtml); - for (const el of $('p,h1,h2,h3,h4,h5').toArray()) { - const text = $(el).text(); - const match = text.match(re); - - if (!match) { - continue; - } - - const note = text.replace(match[1], '').trim(); - return note || $(el).next().text().trim(); - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/index.ts b/packages/kbn-docs-utils/src/release_notes/lib/index.ts deleted file mode 100644 index 8578060007d73..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/index.ts +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './pr_api'; -export * from './version'; -export * from './is_pr_relevant'; -export * from './streams'; -export * from './type_helpers'; -export * from './irrelevant_pr_summary'; -export * from './classify_pr'; diff --git a/packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts b/packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts deleted file mode 100644 index 3bc9ebfced60f..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts +++ /dev/null @@ -1,50 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ToolingLog } from '@kbn/dev-utils'; - -import { PullRequest } from './pr_api'; -import { Version } from './version'; - -export class IrrelevantPrSummary { - private readonly stats = { - 'skipped by label': new Map(), - 'skipped by label regexp': new Map(), - 'skipped by version': new Map(), - }; - - constructor(private readonly log: ToolingLog) {} - - skippedByLabel(pr: PullRequest, label: string) { - this.log.debug(`${pr.terminalLink} skipped, label [${label}] is ignored`); - this.increment('skipped by label', label); - } - - skippedByLabelRegExp(pr: PullRequest, regexp: RegExp, label: string) { - this.log.debug(`${pr.terminalLink} skipped, label [${label}] matches regexp [${regexp}]`); - this.increment('skipped by label regexp', `${regexp}`); - } - - skippedByVersion(pr: PullRequest, earliestVersion: Version) { - this.log.debug(`${pr.terminalLink} skipped, earliest version is [${earliestVersion.label}]`); - this.increment('skipped by version', earliestVersion.label); - } - - private increment(stat: keyof IrrelevantPrSummary['stats'], key: string) { - const n = this.stats[stat].get(key) || 0; - this.stats[stat].set(key, n + 1); - } - - logStats() { - for (const [description, stats] of Object.entries(this.stats)) { - for (const [key, count] of stats) { - this.log.warning(`${count} ${count === 1 ? 'pr was' : 'prs were'} ${description} [${key}]`); - } - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts b/packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts deleted file mode 100644 index 1de75373c0954..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts +++ /dev/null @@ -1,50 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Version } from './version'; -import { PullRequest } from './pr_api'; -import { IGNORE_LABELS } from '../release_notes_config'; -import { IrrelevantPrSummary } from './irrelevant_pr_summary'; - -export function isPrRelevant( - pr: PullRequest, - version: Version, - includeVersions: Version[], - summary: IrrelevantPrSummary -) { - for (const label of IGNORE_LABELS) { - if (typeof label === 'string') { - if (pr.labels.includes(label)) { - summary.skippedByLabel(pr, label); - return false; - } - } - - if (label instanceof RegExp) { - const matching = pr.labels.find((l) => label.test(l)); - if (matching) { - summary.skippedByLabelRegExp(pr, label, matching); - return false; - } - } - } - - const [earliestVersion] = Version.sort( - // filter out `includeVersions` so that they won't be considered the "earliest version", only - // versions which are actually before the current `version` or the `version` itself are eligible - pr.versions.filter((v) => !includeVersions.includes(v)), - 'asc' - ); - - if (version !== earliestVersion) { - summary.skippedByVersion(pr, earliestVersion); - return false; - } - - return true; -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts b/packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts deleted file mode 100644 index 0f4f8abc7fd9c..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts +++ /dev/null @@ -1,222 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { inspect } from 'util'; - -import Axios from 'axios'; -import gql from 'graphql-tag'; -import * as GraphqlPrinter from 'graphql/language/printer'; -import { DocumentNode } from 'graphql/language/ast'; -import makeTerminalLink from 'terminal-link'; -import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils'; - -import { Version } from './version'; -import { getFixReferences } from './get_fix_references'; -import { getNoteFromDescription } from './get_note_from_description'; - -const PrNodeFragment = gql` - fragment PrNode on PullRequest { - number - url - title - bodyText - bodyHTML - mergedAt - baseRefName - state - author { - login - ... on User { - name - } - } - labels(first: 100) { - nodes { - name - } - } - } -`; - -export interface PullRequest { - number: number; - url: string; - title: string; - targetBranch: string; - mergedAt: string; - state: string; - labels: string[]; - fixes: string[]; - user: { - name: string; - login: string; - }; - versions: Version[]; - terminalLink: string; - note?: string; -} - -export class PrApi { - constructor(private readonly log: ToolingLog, private readonly token: string) {} - - async getPr(number: number) { - const resp = await this.gqlRequest( - gql` - query($number: Int!) { - repository(owner: "elastic", name: "kibana") { - pullRequest(number: $number) { - ...PrNode - } - } - } - ${PrNodeFragment} - `, - { - number, - } - ); - - const node = resp.data?.repository?.pullRequest; - if (!node) { - throw new Error(`unexpected github response, unable to fetch PR: ${inspect(resp)}`); - } - - return this.parsePullRequestNode(node); - } - - /** - * Iterate all of the PRs which have the `version` label - */ - async *iterRelevantPullRequests(version: Version) { - let nextCursor: string | undefined; - let hasNextPage = true; - - while (hasNextPage) { - const resp = await this.gqlRequest( - gql` - query($cursor: String, $labels: [String!]) { - repository(owner: "elastic", name: "kibana") { - pullRequests(first: 100, after: $cursor, labels: $labels, states: MERGED) { - pageInfo { - hasNextPage - endCursor - } - nodes { - ...PrNode - } - } - } - } - ${PrNodeFragment} - `, - { - cursor: nextCursor, - labels: [version.label], - } - ); - - const pullRequests = resp.data?.repository?.pullRequests; - if (!pullRequests) { - throw new Error(`unexpected github response, unable to fetch PRs: ${inspect(resp)}`); - } - - hasNextPage = pullRequests.pageInfo?.hasNextPage; - nextCursor = pullRequests.pageInfo?.endCursor; - - if (hasNextPage === undefined || (hasNextPage && !nextCursor)) { - throw new Error( - `github response does not include valid pagination information: ${inspect(resp)}` - ); - } - - for (const node of pullRequests.nodes) { - yield this.parsePullRequestNode(node); - } - } - } - - /** - * Convert the Github API response into the structure used by this tool - * - * @param node A GraphQL response from Github using the PrNode fragment - */ - private parsePullRequestNode(node: any): PullRequest { - const terminalLink = makeTerminalLink(`#${node.number}`, node.url); - - const labels: string[] = node.labels.nodes.map((l: { name: string }) => l.name); - - return { - number: node.number, - url: node.url, - terminalLink, - title: node.title, - targetBranch: node.baseRefName, - state: node.state, - mergedAt: node.mergedAt, - labels, - fixes: getFixReferences(node.bodyText), - user: { - login: node.author?.login || 'deleted user', - name: node.author?.name, - }, - versions: labels - .map((l) => Version.fromLabel(l)) - .filter((v): v is Version => v instanceof Version), - note: - getNoteFromDescription(node.bodyHTML, 'release note') || - getNoteFromDescription(node.bodyHTML, 'dev docs'), - }; - } - - /** - * Send a single request to the Github v4 GraphQL API - */ - private async gqlRequest(query: DocumentNode, variables: Record = {}) { - let attempt = 0; - - while (true) { - attempt += 1; - - try { - const resp = await Axios.request({ - url: 'https://api.github.com/graphql', - method: 'POST', - headers: { - 'user-agent': '@kbn/release-notes', - authorization: `bearer ${this.token}`, - }, - data: { - query: GraphqlPrinter.print(query), - variables, - }, - }); - - return resp.data; - } catch (error) { - if (!isAxiosResponseError(error) || error.response.status < 500) { - // rethrow error unless it is a 500+ response from github - throw error; - } - - const { status, data } = error.response; - const resp = inspect(data); - - if (attempt === 5) { - throw new Error( - `${status} response from Github, attempted request ${attempt} times: [${resp}]` - ); - } - - const delay = attempt * 2000; - this.log.debug(`Github responded with ${status}, retrying in ${delay} ms: [${resp}]`); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/streams.ts b/packages/kbn-docs-utils/src/release_notes/lib/streams.ts deleted file mode 100644 index 6893bfd7f4f44..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/streams.ts +++ /dev/null @@ -1,23 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { promisify } from 'util'; -import { Readable, pipeline } from 'stream'; - -/** - * @types/node still doesn't have this method that was added - * in 10.17.0 https://nodejs.org/api/stream.html#stream_stream_readable_from_iterable_options - */ -export function streamFromIterable( - iter: Iterable | AsyncIterable -): Readable { - // @ts-ignore - return Readable.from(iter); -} - -export const asyncPipeline = promisify(pipeline); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts b/packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts deleted file mode 100644 index 81860160094de..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type ArrayItem = T extends ReadonlyArray ? X : never; diff --git a/packages/kbn-docs-utils/src/release_notes/lib/version.test.ts b/packages/kbn-docs-utils/src/release_notes/lib/version.test.ts deleted file mode 100644 index b23feb0929a2d..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/version.test.ts +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Version } from './version'; - -it('parses version labels, returns null on failure', () => { - expect(Version.fromLabel('v1.0.2')).toMatchInlineSnapshot(` - Version { - "label": "v1.0.2", - "major": 1, - "minor": 0, - "patch": 2, - "tag": undefined, - "tagNum": undefined, - "tagOrder": Infinity, - } - `); - expect(Version.fromLabel('v1.0.0')).toMatchInlineSnapshot(` - Version { - "label": "v1.0.0", - "major": 1, - "minor": 0, - "patch": 0, - "tag": undefined, - "tagNum": undefined, - "tagOrder": Infinity, - } - `); - expect(Version.fromLabel('v9.0.2')).toMatchInlineSnapshot(` - Version { - "label": "v9.0.2", - "major": 9, - "minor": 0, - "patch": 2, - "tag": undefined, - "tagNum": undefined, - "tagOrder": Infinity, - } - `); - expect(Version.fromLabel('v9.0.2-alpha0')).toMatchInlineSnapshot(` - Version { - "label": "v9.0.2-alpha0", - "major": 9, - "minor": 0, - "patch": 2, - "tag": "alpha", - "tagNum": 0, - "tagOrder": 1, - } - `); - expect(Version.fromLabel('v9.0.2-beta1')).toMatchInlineSnapshot(` - Version { - "label": "v9.0.2-beta1", - "major": 9, - "minor": 0, - "patch": 2, - "tag": "beta", - "tagNum": 1, - "tagOrder": 2, - } - `); - expect(Version.fromLabel('v9.0')).toMatchInlineSnapshot(`undefined`); - expect(Version.fromLabel('some:area')).toMatchInlineSnapshot(`undefined`); -}); - -it('sorts versions in ascending order', () => { - const versions = [ - 'v1.7.3', - 'v1.7.0', - 'v1.5.0', - 'v2.7.0', - 'v7.0.0-beta2', - 'v7.0.0-alpha1', - 'v2.0.0', - 'v0.0.0', - 'v7.0.0-beta1', - 'v7.0.0', - ].map((l) => Version.fromLabel(l)!); - - const sorted = Version.sort(versions); - - expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(` - Array [ - "v0.0.0", - "v1.5.0", - "v1.7.0", - "v1.7.3", - "v2.0.0", - "v2.7.0", - "v7.0.0-alpha1", - "v7.0.0-beta1", - "v7.0.0-beta2", - "v7.0.0", - ] - `); - - // ensure versions was not mutated - expect(sorted).not.toEqual(versions); -}); - -it('sorts versions in decending order', () => { - const versions = [ - 'v1.7.3', - 'v1.7.0', - 'v1.5.0', - 'v7.0.0-beta1', - 'v2.7.0', - 'v2.0.0', - 'v0.0.0', - 'v7.0.0', - ].map((l) => Version.fromLabel(l)!); - - const sorted = Version.sort(versions, 'desc'); - - expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(` - Array [ - "v7.0.0", - "v7.0.0-beta1", - "v2.7.0", - "v2.0.0", - "v1.7.3", - "v1.7.0", - "v1.5.0", - "v0.0.0", - ] - `); - - // ensure versions was not mutated - expect(sorted).not.toEqual(versions); -}); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/version.ts b/packages/kbn-docs-utils/src/release_notes/lib/version.ts deleted file mode 100644 index c59060c990220..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/version.ts +++ /dev/null @@ -1,112 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const LABEL_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta)(\d+))?$/; - -const versionCache = new Map(); - -const multiCompare = (...diffs: number[]) => { - for (const diff of diffs) { - if (diff !== 0) { - return diff; - } - } - return 0; -}; - -export class Version { - static fromFlag(flag: string | string[] | boolean | undefined) { - if (typeof flag !== 'string') { - return; - } - - return Version.fromLabel(flag) || Version.fromLabel(`v${flag}`); - } - - static fromFlags(flag: string | string[] | boolean | undefined) { - const flags = Array.isArray(flag) ? flag : [flag]; - const versions: Version[] = []; - - for (const f of flags) { - const version = Version.fromFlag(f); - if (!version) { - return; - } - versions.push(version); - } - - return versions; - } - - static fromLabel(label: string) { - const match = label.match(LABEL_RE); - if (!match) { - return; - } - - const cached = versionCache.get(label); - if (cached) { - return cached; - } - - const [, major, minor, patch, tag, tagNum] = match; - const version = new Version( - parseInt(major, 10), - parseInt(minor, 10), - parseInt(patch, 10), - tag as 'alpha' | 'beta' | undefined, - tagNum ? parseInt(tagNum, 10) : undefined - ); - - versionCache.set(label, version); - return version; - } - - static sort(versions: Version[], dir: 'asc' | 'desc' = 'asc') { - const order = dir === 'asc' ? 1 : -1; - - return versions.slice().sort((a, b) => a.compare(b) * order); - } - - public readonly label = `v${this.major}.${this.minor}.${this.patch}${ - this.tag ? `-${this.tag}${this.tagNum}` : '' - }`; - private readonly tagOrder: number; - - constructor( - public readonly major: number, - public readonly minor: number, - public readonly patch: number, - public readonly tag: 'alpha' | 'beta' | undefined, - public readonly tagNum: number | undefined - ) { - switch (tag) { - case undefined: - this.tagOrder = Infinity; - break; - case 'alpha': - this.tagOrder = 1; - break; - case 'beta': - this.tagOrder = 2; - break; - default: - throw new Error('unexpected tag'); - } - } - - compare(other: Version) { - return multiCompare( - this.major - other.major, - this.minor - other.minor, - this.patch - other.patch, - this.tagOrder - other.tagOrder, - (this.tagNum ?? 0) - (other.tagNum ?? 0) - ); - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/release_notes_config.ts b/packages/kbn-docs-utils/src/release_notes/release_notes_config.ts deleted file mode 100644 index a94dcd8766cbd..0000000000000 --- a/packages/kbn-docs-utils/src/release_notes/release_notes_config.ts +++ /dev/null @@ -1,283 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Exclude any PR from release notes that has a matching label. String - * labels must match exactly, for more complicated use a RegExp - */ -export const IGNORE_LABELS: Array = [ - 'Team:Docs', - ':KibanaApp/fix-it-week', - 'reverted', - /^test/, - 'non-issue', - 'jenkins', - 'build', - 'chore', - 'backport', - 'release_note:skip', - 'release_note:dev_docs', -]; - -/** - * Define areas that are used to categorize changes in the release notes - * based on the labels a PR has. the `labels` array can contain strings, which - * are matched exactly, or regular expressions. The first area, in definition - * order, which has a `label` which matches and label on a PR is the area - * assigned to that PR. - */ - -export interface Area { - title: string; - labels: Array; -} - -export const AREAS: Area[] = [ - { - title: 'Design', - labels: ['Team:Design', 'Project:Accessibility'], - }, - { - title: 'Logstash', - labels: ['App:Logstash', 'Feature:Logstash Pipelines'], - }, - { - title: 'Management', - labels: [ - 'Feature:license', - 'Feature:Console', - 'Feature:Search Profiler', - 'Feature:watcher', - 'Feature:Index Patterns', - 'Feature:Kibana Management', - 'Feature:Dev Tools', - 'Feature:Inspector', - 'Feature:Index Management', - 'Feature:Snapshot and Restore', - 'Team:Elasticsearch UI', - 'Feature:FieldFormatters', - 'Feature:CCR', - 'Feature:ILM', - 'Feature:Transforms', - ], - }, - { - title: 'Monitoring', - labels: ['Team:Monitoring', 'Feature:Telemetry', 'Feature:Stack Monitoring'], - }, - { - title: 'Operations', - labels: ['Team:Operations', 'Feature:License'], - }, - { - title: 'Kibana UI', - labels: ['Kibana UI', 'Team:Core UI', 'Feature:Header'], - }, - { - title: 'Platform', - labels: [ - 'Team:Platform', - 'Feature:Plugins', - 'Feature:New Platform', - 'Project:i18n', - 'Feature:ExpressionLanguage', - 'Feature:Saved Objects', - 'Team:Stack Services', - 'Feature:NP Migration', - 'Feature:Task Manager', - 'Team:Pulse', - ], - }, - { - title: 'Machine Learning', - labels: [ - ':ml', - 'Feature:Anomaly Detection', - 'Feature:Data Frames', - 'Feature:File Data Viz', - 'Feature:ml-results', - 'Feature:Data Frame Analytics', - ], - }, - { - title: 'Maps', - labels: ['Team:Geo'], - }, - { - title: 'QA', - labels: ['Team:QA'], - }, - { - title: 'Security', - labels: [ - 'Team:Security', - 'Feature:Security/Spaces', - 'Feature:users and roles', - 'Feature:Security/Authentication', - 'Feature:Security/Authorization', - 'Feature:Security/Feature Controls', - ], - }, - { - title: 'Canvas', - labels: ['Feature:Canvas'], - }, - { - title: 'Dashboard', - labels: ['Feature:Dashboard', 'Feature:Drilldowns'], - }, - { - title: 'Discover', - labels: ['Feature:Discover'], - }, - { - title: 'Kibana Home & Add Data', - labels: ['Feature:Add Data', 'Feature:Home'], - }, - { - title: 'Querying & Filtering', - labels: [ - 'Feature:Query Bar', - 'Feature:Courier', - 'Feature:Filters', - 'Feature:Timepicker', - 'Feature:Highlight', - 'Feature:KQL', - 'Feature:Rollups', - ], - }, - { - title: 'Reporting', - labels: ['Feature:Reporting', 'Team:Reporting Services'], - }, - { - title: 'Sharing', - labels: ['Feature:Embedding', 'Feature:SharingURLs'], - }, - { - title: 'Visualizations', - labels: [ - 'Feature:Timelion', - 'Feature:TSVB', - 'Feature:Coordinate Map', - 'Feature:Region Map', - 'Feature:Vega', - 'Feature:Gauge Vis', - 'Feature:Tagcloud', - 'Feature:Vis Loader', - 'Feature:Vislib', - 'Feature:Vis Editor', - 'Feature:Aggregations', - 'Feature:Input Control', - 'Feature:Visualizations', - 'Feature:Markdown', - 'Feature:Data Table', - 'Feature:Heatmap', - 'Feature:Pie Chart', - 'Feature:XYAxis', - 'Feature:Graph', - 'Feature:New Feature', - 'Feature:MetricVis', - ], - }, - { - title: 'SIEM', - labels: ['Team:SIEM'], - }, - { - title: 'Code', - labels: ['Team:Code'], - }, - { - title: 'Infrastructure', - labels: ['App:Infrastructure', 'Feature:Infra UI', 'Feature:Service Maps'], - }, - { - title: 'Logs', - labels: ['App:Logs', 'Feature:Logs UI'], - }, - { - title: 'Uptime', - labels: ['App:Uptime', 'Feature:Uptime', 'Team:uptime'], - }, - { - title: 'Beats Management', - labels: ['App:Beats', 'Feature:beats-cm', 'Team:Beats'], - }, - { - title: 'APM', - labels: ['Team:apm', /^apm[:\-]/], - }, - { - title: 'Lens', - labels: ['App:Lens', 'Feature:Lens'], - }, - { - title: 'Alerting', - labels: ['App:Alerting', 'Feature:Alerting', 'Team:Alerting Services', 'Feature:Actions'], - }, - { - title: 'Metrics', - labels: ['App:Metrics', 'Feature:Metrics UI', 'Team:logs-metrics-ui'], - }, - { - title: 'Data ingest', - labels: ['Ingest', 'Feature:Ingest Node Pipelines'], - }, -]; - -export const UNKNOWN_AREA: Area = { - title: 'Unknown', - labels: [], -}; - -/** - * Define the sections that will be assigned to PRs when generating the - * asciidoc formatted report. The order of the sections determines the - * order they will be rendered in the report - */ - -export interface AsciidocSection { - title: string; - labels: Array; - id: string; -} - -export const ASCIIDOC_SECTIONS: AsciidocSection[] = [ - { - id: 'enhancement', - title: 'Enhancements', - labels: ['release_note:enhancement'], - }, - { - id: 'bug', - title: 'Bug fixes', - labels: ['release_note:fix'], - }, - { - id: 'roadmap', - title: 'Roadmap', - labels: ['release_note:roadmap'], - }, - { - id: 'deprecation', - title: 'Deprecations', - labels: ['release_note:deprecation'], - }, - { - id: 'breaking', - title: 'Breaking Changes', - labels: ['release_note:breaking'], - }, -]; - -export const UNKNOWN_ASCIIDOC_SECTION: AsciidocSection = { - id: 'unknown', - title: 'Unknown', - labels: [], -}; diff --git a/scripts/release_notes.js b/scripts/release_notes.js deleted file mode 100644 index 7408ce322677c..0000000000000 --- a/scripts/release_notes.js +++ /dev/null @@ -1,10 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -require('../src/setup_node_env/no_transpilation'); -require('@kbn/docs-utils').runReleaseNotesCli(); diff --git a/yarn.lock b/yarn.lock index f8549853b4781..f40f351dfafb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4881,11 +4881,6 @@ dependencies: "@types/node" "*" -"@types/graphql@^0.13.2": - version "0.13.4" - resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.4.tgz#55ae9c29f0fd6b85ee536f5c72b4769d5c5e06b1" - integrity sha512-B4yel4ro2nTb3v0pYO8vO6SjgvFJSrwUY+IO6TUSLdOSB+gQFslylrhRCHxvXMIhxB71mv5PEE9dAX+24S8sew== - "@types/gulp-zip@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/gulp-zip/-/gulp-zip-4.0.1.tgz#96cd0b994219f9ae3bbbec7ec3baa043fba9d9ef" @@ -14705,18 +14700,6 @@ graphlib@^2.1.8: dependencies: lodash "^4.17.15" -graphql-tag@^2.10.3: - version "2.10.3" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03" - integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA== - -graphql@^0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" - integrity sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog== - dependencies: - iterall "^1.2.1" - grid-index@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" @@ -16770,11 +16753,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterall@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" - integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== - iterate-iterator@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" From fad45022c16e0d33bb81b8e3e5e6d88fcb7747ab Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 21 Apr 2021 12:32:10 -0700 Subject: [PATCH 02/33] [npm] upgrade all jest related dependencies (#96367) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 42 +- .../rendering/rendering_service.test.ts | 10 +- .../web_element_wrapper/custom_cheerio_api.ts | 62 +- .../List/__snapshots__/List.test.tsx.snap | 2611 +++++++---------- x-pack/plugins/lens/jest.config.js | 3 - .../plugins/monitoring/server/config.test.ts | 4 +- .../components/user_info/index.test.tsx | 29 +- .../page_objects/alert_details.ts | 4 +- .../page_objects/triggers_actions_ui_page.ts | 13 +- yarn.lock | 691 ++--- 10 files changed, 1485 insertions(+), 1984 deletions(-) diff --git a/package.json b/package.json index 0ed0d3bbc3b7f..dc2eba88139bb 100644 --- a/package.json +++ b/package.json @@ -435,7 +435,7 @@ "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", "@istanbuljs/schema": "^0.1.2", - "@jest/reporters": "^26.5.2", + "@jest/reporters": "^26.6.2", "@kbn/babel-code-parser": "link:packages/kbn-babel-code-parser", "@kbn/babel-preset": "link:bazel-bin/packages/kbn-babel-preset/npm_module", "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode", @@ -472,11 +472,11 @@ "@storybook/node-logger": "^6.1.20", "@storybook/react": "^6.1.20", "@storybook/theming": "^6.1.20", - "@testing-library/dom": "^7.24.2", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.0.4", - "@testing-library/react-hooks": "^3.4.1", - "@testing-library/user-event": "^12.1.6", + "@testing-library/dom": "^7.30.3", + "@testing-library/jest-dom": "^5.11.10", + "@testing-library/react": "^11.2.6", + "@testing-library/react-hooks": "^5.1.1", + "@testing-library/user-event": "^13.1.1", "@types/accept": "3.1.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", @@ -485,7 +485,7 @@ "@types/base64-js": "^1.2.5", "@types/bluebird": "^3.1.1", "@types/chance": "^1.0.0", - "@types/cheerio": "^0.22.10", + "@types/cheerio": "^0.22.28", "@types/chroma-js": "^1.4.2", "@types/chromedriver": "^81.0.0", "@types/classnames": "^2.2.9", @@ -505,7 +505,7 @@ "@types/delete-empty": "^2.0.0", "@types/ejs": "^3.0.6", "@types/elasticsearch": "^5.0.33", - "@types/enzyme": "^3.10.5", + "@types/enzyme": "^3.10.8", "@types/eslint": "^6.1.3", "@types/extract-zip": "^1.6.2", "@types/faker": "^5.1.5", @@ -533,9 +533,9 @@ "@types/http-proxy-agent": "^2.0.2", "@types/inquirer": "^7.3.1", "@types/intl-relativeformat": "^2.1.0", - "@types/jest": "^26.0.14", - "@types/jest-specific-snapshot": "^0.5.4", - "@types/jest-when": "^2.7.1", + "@types/jest": "^26.0.22", + "@types/jest-specific-snapshot": "^0.5.5", + "@types/jest-when": "^2.7.2", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", "@types/js-search": "^1.4.0", @@ -614,8 +614,8 @@ "@types/tar": "^4.0.3", "@types/tar-fs": "^1.16.1", "@types/tempy": "^0.2.0", - "@types/testing-library__jest-dom": "^5.9.3", - "@types/testing-library__react-hooks": "^3.4.0", + "@types/testing-library__jest-dom": "^5.9.5", + "@types/testing-library__react-hooks": "^4.0.0", "@types/tinycolor2": "^1.4.1", "@types/type-detect": "^4.0.1", "@types/use-resize-observer": "^6.0.0", @@ -678,9 +678,9 @@ "dpdm": "3.5.0", "ejs": "^3.1.6", "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.2", - "enzyme-adapter-utils": "^1.13.0", - "enzyme-to-json": "^3.4.4", + "enzyme-adapter-react-16": "^1.15.6", + "enzyme-adapter-utils": "^1.14.0", + "enzyme-to-json": "^3.6.1", "eslint": "^6.8.0", "eslint-config-prettier": "^6.15.0", "eslint-import-resolver-node": "0.3.2", @@ -691,7 +691,7 @@ "eslint-plugin-cypress": "^2.11.2", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.0.2", + "eslint-plugin-jest": "^24.3.4", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-mocha": "^6.2.2", "eslint-plugin-no-unsanitized": "^3.0.2", @@ -727,18 +727,18 @@ "is-path-inside": "^3.0.2", "istanbul-instrumenter-loader": "^3.0.1", "jest": "^26.6.3", - "jest-canvas-mock": "^2.2.0", + "jest-canvas-mock": "^2.3.1", "jest-circus": "^26.6.3", "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", "jest-environment-jsdom": "^26.6.2", "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", - "jest-silent-reporter": "^0.2.1", + "jest-silent-reporter": "^0.5.0", "jest-snapshot": "^26.6.2", "jest-specific-snapshot": "2.0.0", - "jest-styled-components": "^7.0.2", - "jest-when": "^2.7.2", + "jest-styled-components": "^7.0.3", + "jest-when": "^3.2.1", "jimp": "^0.14.0", "jsdom": "13.1.0", "json5": "^1.0.1", diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 65df5cd6aa312..bba0dc6fd8a67 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -80,7 +80,7 @@ describe('RenderingService', () => { it('renders "core" page', async () => { const content = await render(createKibanaRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -90,7 +90,7 @@ describe('RenderingService', () => { const content = await render(createKibanaRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -99,7 +99,7 @@ describe('RenderingService', () => { uiSettings.getUserProvided.mockResolvedValue({ 'theme:darkMode': { userValue: true } }); const content = await render(createKibanaRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -109,7 +109,7 @@ describe('RenderingService', () => { includeUserSettings: false, }); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -117,7 +117,7 @@ describe('RenderingService', () => { it('renders "core" from legacy request', async () => { const content = await render(createRawRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); diff --git a/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts b/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts index 301eb656ed6f6..c01e07fd07624 100644 --- a/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts +++ b/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts @@ -8,13 +8,13 @@ interface CheerioSelector { (selector: string): CustomCheerio; (selector: string, context: string): CustomCheerio; - (selector: string, context: CheerioElement): CustomCheerio; - (selector: string, context: CheerioElement[]): CustomCheerio; - (selector: string, context: Cheerio): CustomCheerio; + (selector: string, context: cheerio.Element): CustomCheerio; + (selector: string, context: cheerio.Element[]): CustomCheerio; + (selector: string, context: cheerio.Cheerio): CustomCheerio; (selector: string, context: string, root: string): CustomCheerio; - (selector: string, context: CheerioElement, root: string): CustomCheerio; - (selector: string, context: CheerioElement[], root: string): CustomCheerio; - (selector: string, context: Cheerio, root: string): CustomCheerio; + (selector: string, context: cheerio.Element, root: string): CustomCheerio; + (selector: string, context: cheerio.Element[], root: string): CustomCheerio; + (selector: string, context: cheerio.Cheerio, root: string): CustomCheerio; (selector: any): CustomCheerio; } @@ -24,13 +24,13 @@ export interface CustomCheerioStatic extends CheerioSelector { // JQuery http://api.jquery.com xml(): string; root(): CustomCheerio; - contains(container: CheerioElement, contained: CheerioElement): boolean; + contains(container: cheerio.Element, contained: cheerio.Element): boolean; parseHTML(data: string, context?: Document, keepScripts?: boolean): Document[]; - html(options?: CheerioOptionsInterface): string; - html(selector: string, options?: CheerioOptionsInterface): string; - html(element: CustomCheerio, options?: CheerioOptionsInterface): string; - html(element: CheerioElement, options?: CheerioOptionsInterface): string; + html(options?: cheerio.CheerioParserOptions): string; + html(selector: string, options?: cheerio.CheerioParserOptions): string; + html(element: CustomCheerio, options?: cheerio.CheerioParserOptions): string; + html(element: cheerio.Element, options?: cheerio.CheerioParserOptions): string; // // CUSTOM METHODS @@ -44,7 +44,7 @@ export interface CustomCheerio { // Cheerio https://github.com/cheeriojs/cheerio // JQuery http://api.jquery.com - [index: number]: CheerioElement; + [index: number]: cheerio.Element; length: number; // Attributes @@ -63,7 +63,7 @@ export interface CustomCheerio { removeAttr(name: string): CustomCheerio; has(selector: string): CustomCheerio; - has(element: CheerioElement): CustomCheerio; + has(element: cheerio.Element): CustomCheerio; hasClass(className: string): boolean; addClass(classNames: string): CustomCheerio; @@ -81,10 +81,10 @@ export interface CustomCheerio { ): CustomCheerio; is(selector: string): boolean; - is(element: CheerioElement): boolean; - is(element: CheerioElement[]): boolean; + is(element: cheerio.Element): boolean; + is(element: cheerio.Element[]): boolean; is(selection: CustomCheerio): boolean; - is(func: (index: number, element: CheerioElement) => boolean): boolean; + is(func: (index: number, element: cheerio.Element) => boolean): boolean; // Form serialize(): string; @@ -98,7 +98,7 @@ export interface CustomCheerio { parent(selector?: string): CustomCheerio; parents(selector?: string): CustomCheerio; parentsUntil(selector?: string, filter?: string): CustomCheerio; - parentsUntil(element: CheerioElement, filter?: string): CustomCheerio; + parentsUntil(element: cheerio.Element, filter?: string): CustomCheerio; parentsUntil(element: CustomCheerio, filter?: string): CustomCheerio; prop(name: string): any; @@ -112,7 +112,7 @@ export interface CustomCheerio { nextAll(selector: string): CustomCheerio; nextUntil(selector?: string, filter?: string): CustomCheerio; - nextUntil(element: CheerioElement, filter?: string): CustomCheerio; + nextUntil(element: cheerio.Element, filter?: string): CustomCheerio; nextUntil(element: CustomCheerio, filter?: string): CustomCheerio; prev(selector?: string): CustomCheerio; @@ -120,7 +120,7 @@ export interface CustomCheerio { prevAll(selector: string): CustomCheerio; prevUntil(selector?: string, filter?: string): CustomCheerio; - prevUntil(element: CheerioElement, filter?: string): CustomCheerio; + prevUntil(element: cheerio.Element, filter?: string): CustomCheerio; prevUntil(element: CustomCheerio, filter?: string): CustomCheerio; slice(start: number, end?: number): CustomCheerio; @@ -131,19 +131,19 @@ export interface CustomCheerio { contents(): CustomCheerio; - each(func: (index: number, element: CheerioElement) => any): CustomCheerio; - map(func: (index: number, element: CheerioElement) => any): CustomCheerio; + each(func: (index: number, element: cheerio.Element) => any): CustomCheerio; + map(func: (index: number, element: cheerio.Element) => any): CustomCheerio; filter(selector: string): CustomCheerio; filter(selection: CustomCheerio): CustomCheerio; - filter(element: CheerioElement): CustomCheerio; - filter(elements: CheerioElement[]): CustomCheerio; - filter(func: (index: number, element: CheerioElement) => boolean): CustomCheerio; + filter(element: cheerio.Element): CustomCheerio; + filter(elements: cheerio.Element[]): CustomCheerio; + filter(func: (index: number, element: cheerio.Element) => boolean): CustomCheerio; not(selector: string): CustomCheerio; not(selection: CustomCheerio): CustomCheerio; - not(element: CheerioElement): CustomCheerio; - not(func: (index: number, element: CheerioElement) => boolean): CustomCheerio; + not(element: cheerio.Element): CustomCheerio; + not(func: (index: number, element: cheerio.Element) => boolean): CustomCheerio; first(): CustomCheerio; last(): CustomCheerio; @@ -161,8 +161,8 @@ export interface CustomCheerio { add(selectorOrHtml: string): CustomCheerio; add(selector: string, context: Document): CustomCheerio; - add(element: CheerioElement): CustomCheerio; - add(elements: CheerioElement[]): CustomCheerio; + add(element: cheerio.Element): CustomCheerio; + add(elements: cheerio.Element[]): CustomCheerio; add(selection: CustomCheerio): CustomCheerio; addBack(): CustomCheerio; @@ -203,8 +203,8 @@ export interface CustomCheerio { remove(selector?: string): CustomCheerio; replaceWith(content: string): CustomCheerio; - replaceWith(content: CheerioElement): CustomCheerio; - replaceWith(content: CheerioElement[]): CustomCheerio; + replaceWith(content: cheerio.Element): CustomCheerio; + replaceWith(content: cheerio.Element[]): CustomCheerio; replaceWith(content: CustomCheerio): CustomCheerio; replaceWith(content: () => CustomCheerio): CustomCheerio; @@ -236,7 +236,7 @@ export interface CustomCheerio { // Not Documented - toArray(): CheerioElement[]; + toArray(): CustomCheerio[]; // // CUSTOM METHODS diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap index a3074bf66a052..58d5287e084ca 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap @@ -1,352 +1,278 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ErrorGroupOverview -> List should render empty state 1`] = ` - - Group ID - - - , - "render": [Function], - "sortable": false, - "width": "96px", - }, - Object { - "field": "type", - "name": "Type", - "render": [Function], - "sortable": false, - }, - Object { - "field": "message", - "name": "Error message and culprit", - "render": [Function], - "sortable": false, - "width": "50%", - }, - Object { - "align": "right", - "field": "handled", - "name": "", - "render": [Function], - "sortable": false, - }, - Object { - "dataType": "number", - "field": "occurrenceCount", - "name": "Occurrences", - "render": [Function], - "sortable": true, - }, - Object { - "align": "right", - "field": "latestOccurrenceAt", - "name": "Latest occurrence", - "render": [Function], - "sortable": true, - }, - ] - } - initialPageSize={25} - initialSortDirection="desc" - initialSortField="occurrenceCount" - items={Array []} - noItemsMessage="No errors were found" - sortItems={false} +
-
-
+
+
- +
-
-
-
- -
-
+ Sorting + + +
- +
- - - - + +
-
+ + + + - + - + - - - + - - - - - + + + + - - -
+
+
-
+ Group ID + - Group ID - - - + aria-label="Info" + className="eui-alignTop" + color="subdued" + data-euiicon-type="questionInCircle" + onBlur={[Function]} + onFocus={[Function]} + size="s" + tabIndex={0} + /> -
-
+ + +
-
- - Type - -
-
+ + +
-
- - Error message and culprit - -
-
+ + + +
-
- -
-
+ + + + - + + + Click to sort in ascending order + + + + -
+ + Click to sort in ascending order + + + +
+
-
- - No errors were found - -
-
- + No errors were found + + + +
- +
`; exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -381,590 +307,524 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; } - - Group ID - - - , - "render": [Function], - "sortable": false, - "width": "96px", - }, - Object { - "field": "type", - "name": "Type", - "render": [Function], - "sortable": false, - }, - Object { - "field": "message", - "name": "Error message and culprit", - "render": [Function], - "sortable": false, - "width": "50%", - }, - Object { - "align": "right", - "field": "handled", - "name": "", - "render": [Function], - "sortable": false, - }, - Object { - "dataType": "number", - "field": "occurrenceCount", - "name": "Occurrences", - "render": [Function], - "sortable": true, - }, - Object { - "align": "right", - "field": "latestOccurrenceAt", - "name": "Latest occurrence", - "render": [Function], - "sortable": true, - }, - ] - } - initialPageSize={25} - initialSortDirection="desc" - initialSortField="occurrenceCount" - items={ - Array [ - Object { - "culprit": "elasticapm.contrib.django.client.capture", - "groupId": "a0ce2c8978ef92cdf2ff163ae28576ee", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:37.561Z", - "message": "About to blow up!", - "occurrenceCount": 75, - "type": "AssertionError", - }, - Object { - "culprit": "opbeans.views.oopsie", - "groupId": "f3ac95493913cc7a3cfec30a19d2120a", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:37.630Z", - "message": "AssertionError: ", - "occurrenceCount": 75, - "type": "AssertionError", - }, - Object { - "culprit": "opbeans.tasks.update_stats", - "groupId": "e90863d04b7a692435305f09bbe8c840", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:36.859Z", - "message": "AssertionError: Bad luck!", - "occurrenceCount": 24, - "type": "AssertionError", - }, - Object { - "culprit": "opbeans.views.customer", - "groupId": "8673d8bf7a032e387c101bafbab0d2bc", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:13.211Z", - "message": "Customer with ID 8517 not found", - "occurrenceCount": 15, - "type": "AssertionError", - }, - ] - } - noItemsMessage="No errors were found" - sortItems={false} +
-
-
+
+
- +
-
-
-
- -
-
+ Sorting + + +
- +
- - - - + +
-
+ + + + - + - + - + + - + + + + + - - - + + + + + + - + + + - + + + - + - - + - - - - + + + - + + + - + - - + - - - - + + + - + + + - + - - + - - - - + - + - - + - - - -
+
+
-
+ Group ID + - Group ID - - - + aria-label="Info" + className="eui-alignTop" + color="subdued" + data-euiicon-type="questionInCircle" + onBlur={[Function]} + onFocus={[Function]} + size="s" + tabIndex={0} + /> -
-
+ + +
-
- - Type - -
-
+ + +
-
- - Error message and culprit - -
-
+ + + +
+ +
+
+ + + Click to sort in ascending order + + + + - + + Click to sort in ascending order + + +
+
- - -
- + Type + + + +
+ Error message and culprit +
+
- Group ID - - + rel="noreferrer" + > + About to blow up! + -
-
- + - - - a0ce2 - - - + elasticapm.contrib.django.client.capture +
+
-
+ +
+
+
-
- Type -
-
+
+ 75 +
+
+
+ Latest occurrence +
+
+ - - - - AssertionError - - - -
-
+ +
+
-
- Error message and culprit -
-
+ +
+
+ - -
- - - - - About to blow up! - - - - -
- - -
- elasticapm.contrib.django.client.capture -
-
-
-
- -
-
+ + +
-
-
+
-
- Occurrences -
-
- 75 -
-
+ + +
+ Error message and culprit +
+
- Latest occurrence -
-
List should render with data 1`] = ` onMouseOut={[Function]} onMouseOver={[Function]} > - 1337 minutes ago (mocking 1515578797) + + AssertionError: + -
-
-
- Group ID - +
- - -
-
- - - - f3ac9 - - - + opbeans.views.oopsie +
+ -
+ +
+
+
-
- Type -
-
+
+ 75 +
+
+
+ Latest occurrence +
+
+ - - - - AssertionError - - - -
-
+ +
+
-
- Error message and culprit -
-
+ +
+
+ - -
- - - - - AssertionError: - - - - -
- - -
- opbeans.views.oopsie -
-
-
-
- -
-
+ + +
-
-
+
-
- Occurrences -
-
- 75 -
-
+ + +
+ Error message and culprit +
+
- Latest occurrence -
-
List should render with data 1`] = ` onMouseOut={[Function]} onMouseOver={[Function]} > - 1337 minutes ago (mocking 1515578797) + + AssertionError: Bad luck! + -
-
-
- Group ID - +
- - -
-
- - - - e9086 - - - + opbeans.tasks.update_stats +
+ -
+ +
+
+
-
- Type -
-
+
+ 24 +
+
+
+ Latest occurrence +
+
+ - - - - AssertionError - - - -
-
+ +
+
-
- Error message and culprit -
-
+ +
+
+ - -
- - - - - AssertionError: Bad luck! - - - - -
- - -
- opbeans.tasks.update_stats -
-
-
-
- -
-
+ + +
-
-
+
-
- Occurrences -
-
- 24 -
-
+ + +
+ Error message and culprit +
+
- Latest occurrence -
-
List should render with data 1`] = ` onMouseOut={[Function]} onMouseOver={[Function]} > - 1337 minutes ago (mocking 1515578796) + + Customer with ID 8517 not found + -
-
-
- Group ID - +
- - -
-
- - - - 8673d - - - + opbeans.views.customer +
+ -
+ -
- Type -
- -
+
+
+
-
- Error message and culprit -
-
- -
- - - - - Customer with ID 8517 not found - - - - -
- - -
- opbeans.views.customer -
-
-
-
-
-
-
+
-
-
+ +
-
- Occurrences -
-
- 15 -
-
+
-
- Latest occurrence -
-
- - 1337 minutes ago (mocking 1515578773) - -
-
- -
+ 1337 minutes ago (mocking 1515578773) + +
+ +
+
+
+
+
- -
-
-
+
-
- + + + + +
- +
`; diff --git a/x-pack/plugins/lens/jest.config.js b/x-pack/plugins/lens/jest.config.js index 9a3f12e1ead32..615e540eaedce 100644 --- a/x-pack/plugins/lens/jest.config.js +++ b/x-pack/plugins/lens/jest.config.js @@ -9,7 +9,4 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/lens'], - - // TODO: migrate to "jest-environment-jsdom" https://github.com/elastic/kibana/issues/95202 - testEnvironment: 'jest-environment-jsdom-thirteen', }; diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts index c285ff27c5a63..8ea37d04c146c 100644 --- a/x-pack/plugins/monitoring/server/config.test.ts +++ b/x-pack/plugins/monitoring/server/config.test.ts @@ -19,7 +19,9 @@ const MOCKED_PATHS = [ beforeEach(() => { const spy = jest.spyOn(fs, 'readFileSync').mockImplementation(); - MOCKED_PATHS.forEach((file) => when(spy).calledWith(file).mockReturnValue(`contents-of-${file}`)); + MOCKED_PATHS.forEach((file) => + when(spy).calledWith(file, 'utf8').mockReturnValue(`contents-of-${file}`) + ); }); describe('config schema', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx index 0441cee8d2cf7..08612e0b6d00d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx @@ -33,22 +33,21 @@ describe('useUserInfo', () => { const { result, waitForNextUpdate } = renderHook(() => useUserInfo()); await waitForNextUpdate(); - expect(result).toEqual({ - current: { - canUserCRUD: null, - hasEncryptionKey: null, - hasIndexManage: null, - hasIndexMaintenance: null, - hasIndexWrite: null, - hasIndexUpdateDelete: null, - isAuthenticated: null, - isSignalIndexExists: null, - loading: true, - signalIndexName: null, - signalIndexMappingOutdated: null, - }, - error: undefined, + expect(result.all).toHaveLength(1); + expect(result.current).toEqual({ + canUserCRUD: null, + hasEncryptionKey: null, + hasIndexManage: null, + hasIndexMaintenance: null, + hasIndexWrite: null, + hasIndexUpdateDelete: null, + isAuthenticated: null, + isSignalIndexExists: null, + loading: true, + signalIndexName: null, + signalIndexMappingOutdated: null, }); + expect(result.error).toBeUndefined(); }); }); diff --git a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts index 98272294b330d..8740deaeddd6e 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts @@ -33,7 +33,7 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { const $ = await table.parseDomContent(); return $.findTestSubjects('alert-instance-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { return { instance: $(row) .findTestSubject('alertInstancesTableCell-instance') @@ -87,7 +87,7 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { $.findTestSubjects('alert-instance-row') .toArray() .filter( - (row: CheerioElement) => + (row) => $(row) .findTestSubject('alertInstancesTableCell-instance') .find('.euiTableCellContent') diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index e5971ddba415f..5fa442e289037 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { CustomCheerioStatic } from 'test/functional/services/lib/web_element_wrapper/custom_cheerio_api'; +import { + CustomCheerio, + CustomCheerioStatic, +} from 'test/functional/services/lib/web_element_wrapper/custom_cheerio_api'; import { FtrProviderContext } from '../ftr_provider_context'; const ENTER_KEY = '\uE007'; @@ -16,7 +19,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const retry = getService('retry'); const testSubjects = getService('testSubjects'); - function getRowItemData(row: CheerioElement, $: CustomCheerioStatic) { + function getRowItemData(row: CustomCheerio, $: CustomCheerioStatic) { return { name: $(row).findTestSubject('alertsTableCell-name').find('.euiTableCellContent').text(), tagsText: $(row) @@ -79,7 +82,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const $ = await table.parseDomContent(); return $.findTestSubjects('connectors-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { return { name: $(row) .findTestSubject('connectorsTableCell-name') @@ -97,7 +100,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const $ = await table.parseDomContent(); return $.findTestSubjects('alert-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { return getRowItemData(row, $); }); }, @@ -106,7 +109,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const $ = await table.parseDomContent(); return $.findTestSubjects('alert-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { const rowItem = getRowItemData(row, $); return { ...rowItem, diff --git a/yarn.lock b/yarn.lock index f40f351dfafb9..f6ce8d8093607 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1147,7 +1147,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== @@ -2071,7 +2071,7 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/console@^26.5.2", "@jest/console@^26.6.2": +"@jest/console@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== @@ -2127,15 +2127,6 @@ "@types/node" "*" jest-mock "^26.6.2" -"@jest/fake-timers@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" - integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== - dependencies: - "@jest/types" "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - "@jest/fake-timers@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" @@ -2157,38 +2148,6 @@ "@jest/types" "^26.6.2" expect "^26.6.2" -"@jest/reporters@^26.5.2": - version "26.5.3" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.5.3.tgz#e810e9c2b670f33f1c09e9975749260ca12f1c17" - integrity sha512-X+vR0CpfMQzYcYmMFKNY9n4jklcb14Kffffp7+H/MqitWnb0440bW2L76NGWKAa+bnXhNoZr+lCVtdtPmfJVOQ== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.5.2" - "@jest/test-result" "^26.5.2" - "@jest/transform" "^26.5.2" - "@jest/types" "^26.5.2" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.5.2" - jest-resolve "^26.5.2" - jest-util "^26.5.2" - jest-worker "^26.5.0" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^6.0.1" - optionalDependencies: - node-notifier "^8.0.0" - "@jest/reporters@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" @@ -2248,7 +2207,7 @@ "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-result@^26.5.2", "@jest/test-result@^26.6.2": +"@jest/test-result@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== @@ -2269,7 +2228,7 @@ jest-runner "^26.6.3" jest-runtime "^26.6.3" -"@jest/transform@^26.0.0", "@jest/transform@^26.5.2", "@jest/transform@^26.6.2": +"@jest/transform@^26.0.0", "@jest/transform@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== @@ -2309,7 +2268,7 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.5.2", "@jest/types@^26.6.2": +"@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== @@ -4292,23 +4251,24 @@ resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9" integrity sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw== -"@testing-library/dom@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.2.tgz#6d2b7dd21efbd5358b98c2777fc47c252f3ae55e" - integrity sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA== +"@testing-library/dom@^7.28.1", "@testing-library/dom@^7.30.3": + version "7.30.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519" + integrity sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.10.3" + "@babel/runtime" "^7.12.5" "@types/aria-query" "^4.2.0" aria-query "^4.2.2" chalk "^4.1.0" - dom-accessibility-api "^0.5.1" - pretty-format "^26.4.2" + dom-accessibility-api "^0.5.4" + lz-string "^1.4.4" + pretty-format "^26.6.2" -"@testing-library/jest-dom@^5.11.4": - version "5.11.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.4.tgz#f325c600db352afb92995c2576022b35621ddc99" - integrity sha512-6RRn3epuweBODDIv3dAlWjOEHQLpGJHB2i912VS3JQtsD22+ENInhdDNl4ZZQiViLlIfFinkSET/J736ytV9sw== +"@testing-library/jest-dom@^5.11.10": + version "5.11.10" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.10.tgz#1cd90715023e1627f5ed26ab3b38e6f22d77046c" + integrity sha512-FuKiq5xuk44Fqm0000Z9w0hjOdwZRNzgx7xGGxQYepWFZy+OYUMOT/wPI4nLYXCaVltNVpU1W/qmD88wLWDsqQ== dependencies: "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" @@ -4319,28 +4279,32 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react-hooks@^3.4.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.4.1.tgz#1f8ccd21208086ec228d9743fe40b69d0efcd7e5" - integrity sha512-LbzvE7oKsVzuW1cxA/aOeNgeVvmHWG2p/WSzalIGyWuqZT3jVcNDT5KPEwy36sUYWde0Qsh32xqIUFXukeywXg== +"@testing-library/react-hooks@*", "@testing-library/react-hooks@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-5.1.1.tgz#1fbaae8a4e8a4a7f97b176c23e1e890c41bbbfa5" + integrity sha512-52D2XnpelFDefnWpy/V6z2qGNj8JLIvW5DjYtelMvFXdEyWiykSaI7IXHwFy4ICoqXJDmmwHAiFRiFboub/U5g== dependencies: - "@babel/runtime" "^7.5.4" - "@types/testing-library__react-hooks" "^3.3.0" + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + filter-console "^0.1.1" + react-error-boundary "^3.1.0" -"@testing-library/react@^11.0.4": - version "11.0.4" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.4.tgz#c84082bfe1593d8fcd475d46baee024452f31dee" - integrity sha512-U0fZO2zxm7M0CB5h1+lh31lbAwMSmDMEMGpMT3BUPJwIjDEKYWOV4dx7lb3x2Ue0Pyt77gmz/VropuJnSz/Iew== +"@testing-library/react@^11.2.6": + version "11.2.6" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" + integrity sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ== dependencies: - "@babel/runtime" "^7.11.2" - "@testing-library/dom" "^7.24.2" + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" -"@testing-library/user-event@^12.1.6": - version "12.1.6" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-12.1.6.tgz#f550b138dfdc20387b89cbe3e9f3d969ab10c2bd" - integrity sha512-BdSe6cmzDEapTBH3s1NKbzu+GyX5bJKraKwVpM2vZF1+EEWxZr0EiA0z9bA5Nux8P+6nKMOZKsXQrj5q/kicfQ== +"@testing-library/user-event@^13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.1.1.tgz#1e011de944cf4d2a917cef6c3046c26389943e24" + integrity sha512-B4roX+0mpXKGj8ndd38YoIo3IV9pmTTWxr/2cOke5apTtrNabEUE0KMBccpcAcYlfPcr7uMu+dxeeC3HdXd9qQ== dependencies: - "@babel/runtime" "^7.10.2" + "@babel/runtime" "^7.12.5" "@ts-morph/common@~0.7.0": version "0.7.3" @@ -4621,10 +4585,12 @@ resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" integrity sha512-jtV6Bv/j+xk4gcXeLlESwNc/m/I/dIZA0xrt29g0uKcjyPob8iisj/5z0ARE+Ldfx4MxjNFNECG0z++J7zJgqg== -"@types/cheerio@*", "@types/cheerio@^0.22.10": - version "0.22.10" - resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6" - integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg== +"@types/cheerio@*", "@types/cheerio@^0.22.22", "@types/cheerio@^0.22.28": + version "0.22.28" + resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.28.tgz#90808aabb44fec40fa2950f4c72351e3e4eb065b" + integrity sha512-ehUMGSW5IeDxJjbru4awKYMlKGmo1wSSGUVqXtYwlgmUM8X1a0PZttEIm6yEY7vHsY/hh6iPnklF213G0UColw== + dependencies: + "@types/node" "*" "@types/chroma-js@^1.4.2": version "1.4.2" @@ -4763,10 +4729,10 @@ resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.33.tgz#b0fd37dc674f498223b6d68c313bdfd71f4d812b" integrity sha512-n/g9pqJEpE4fyUE8VvHNGtl7E2Wv8TCroNwfgAeJKRV4ghDENahtrAo1KMsFNIejBD2gDAlEUa4CM4oEEd8p9Q== -"@types/enzyme@^3.10.5": - version "3.10.5" - resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0" - integrity sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA== +"@types/enzyme@^3.10.8": + version "3.10.8" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.8.tgz#ad7ac9d3af3de6fd0673773123fafbc63db50d42" + integrity sha512-vlOuzqsTHxog6PV79+tvOHFb6hq4QZKMq1lLD9MaWD1oec2lHTKndn76XOpSwCA0oFTaIbKVPrgM3k78Jjd16g== dependencies: "@types/cheerio" "*" "@types/react" "*" @@ -5063,27 +5029,27 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest-specific-snapshot@^0.5.3", "@types/jest-specific-snapshot@^0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@types/jest-specific-snapshot/-/jest-specific-snapshot-0.5.4.tgz#997364c39a59ddeff0ee790a19415e79dd061d1e" - integrity sha512-1qISn4fH8wkOOPFEx+uWRRjw6m/pP/It3OHLm8Ee1KQpO7Z9ZGYDtWPU5AgK05UXsNTAgOK+dPQvJKGdy9E/1g== +"@types/jest-specific-snapshot@^0.5.3", "@types/jest-specific-snapshot@^0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@types/jest-specific-snapshot/-/jest-specific-snapshot-0.5.5.tgz#47ce738870be99898ed6d7b08dbf0240c74ae553" + integrity sha512-AaPPw2tE8ewfjD6qGLkEd4DOfM6pPOK7ob/RSOe1Z8Oo70r9Jgo0SlWyfxslPAOvLfQukQtiVPm6DcnjSoZU5A== dependencies: "@types/jest" "*" -"@types/jest-when@^2.7.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@types/jest-when/-/jest-when-2.7.1.tgz#0b04a33a48a17370c390e9830a975822b3ac5e32" - integrity sha512-PRrGzDkU859cdkFL2KwWN4fRLRDGIUkRNT0StbthhKmj+naU4wImpoJeMnhjprvSou4pKAzU0dKfdQvjceJVhg== +"@types/jest-when@^2.7.2": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/jest-when/-/jest-when-2.7.2.tgz#619fbc5f623bcd0b29efde0e4993c7f0d50d026d" + integrity sha512-vOtj0cev6vO1VX7Jbfg/qvy+sfLI64STsHbKVkggK+1kd11rcMGzFpZKBxUvQfsm4JRULCBISu+qrfs7fYZFGg== dependencies: "@types/jest" "*" -"@types/jest@*", "@types/jest@^26.0.14": - version "26.0.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" - integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg== +"@types/jest@*", "@types/jest@^26.0.22": + version "26.0.22" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" + integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" + jest-diff "^26.0.0" + pretty-format "^26.0.0" "@types/jest@^25.1.1": version "25.2.3" @@ -5559,7 +5525,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@^16.9.8": +"@types/react-dom@>=16.9.0", "@types/react-dom@^16.9.8": version "16.9.8" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== @@ -5634,7 +5600,7 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@*", "@types/react-test-renderer@^16.9.1": +"@types/react-test-renderer@>=16.9.0", "@types/react-test-renderer@^16.9.1": version "16.9.1" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4" integrity sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ== @@ -5663,7 +5629,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.9.36": +"@types/react@*", "@types/react@>=16.9.0", "@types/react@^16.9.36": version "16.9.36" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.36.tgz#ade589ff51e2a903e34ee4669e05dbfa0c1ce849" integrity sha512-mGgUb/Rk/vGx4NCvquRuSH0GHBQKb1OqpGS9cT9lFxlTLHZgkksgI60TuIxubmn7JuCb+sENHhQciqa0npm0AQ== @@ -5854,19 +5820,19 @@ resolved "https://registry.yarnpkg.com/@types/tempy/-/tempy-0.2.0.tgz#8b7a93f6912aef25cc0b8d8a80ff974151478685" integrity sha512-YaX74QljqR45Xu7dd22wMvzTS+ItUiSyDl9XJl6WTgYNE09r2TF+mV2FDjWRM5Sdzf9C9dXRTUdz9J5SoEYxXg== -"@types/testing-library__jest-dom@^5.9.1", "@types/testing-library__jest-dom@^5.9.3": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.3.tgz#574039e210140a536c6ec891063289fb742a75eb" - integrity sha512-5YxiCFA2vk0cxq2LIxYgHBpFlnJvMH9bkUIVNin+1GXT+LZgVOgXBeEyyo2ZrGXMO/KWe1ZV3p7Kb6LJAvJasw== +"@types/testing-library__jest-dom@^5.9.1", "@types/testing-library__jest-dom@^5.9.5": + version "5.9.5" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz#5bf25c91ad2d7b38f264b12275e5c92a66d849b0" + integrity sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ== dependencies: "@types/jest" "*" -"@types/testing-library__react-hooks@^3.3.0", "@types/testing-library__react-hooks@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.4.0.tgz#be148b7fa7d19cd3349c4ef9d9534486bc582fcc" - integrity sha512-QYLZipqt1hpwYsBU63Ssa557v5wWbncqL36No59LI7W3nCMYKrLWTnYGn2griZ6v/3n5nKXNYkTeYpqPHY7Ukg== +"@types/testing-library__react-hooks@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-4.0.0.tgz#2612eabbbb762968985fc1aa35f979caaa78f118" + integrity sha512-UzZUXthQtVjDruR2YA+hqg9ux5AfmZ8Kaw+QDungax+T7wb/5NC4x7YOpIqRx7oY3KksGQ69bzNE/xwzb5NslQ== dependencies: - "@types/react-test-renderer" "*" + "@testing-library/react-hooks" "*" "@types/through@*": version "0.0.30" @@ -6539,21 +6505,20 @@ airbnb-js-shims@^2.2.1: string.prototype.padstart "^3.0.0" symbol.prototype.description "^1.0.0" -airbnb-prop-types@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" - integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== +airbnb-prop-types@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" + integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== dependencies: - array.prototype.find "^2.1.0" - function.prototype.name "^1.1.1" - has "^1.0.3" - is-regex "^1.0.4" - object-is "^1.0.1" + array.prototype.find "^2.1.1" + function.prototype.name "^1.1.2" + is-regex "^1.1.0" + object-is "^1.1.2" object.assign "^4.1.0" - object.entries "^1.1.0" + object.entries "^1.1.2" prop-types "^15.7.2" prop-types-exact "^1.2.0" - react-is "^16.9.0" + react-is "^16.13.1" ajv-errors@^1.0.0: version "1.0.0" @@ -7115,13 +7080,13 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.0.tgz#630f2eaf70a39e608ac3573e45cf8ccd0ede9ad7" - integrity sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg== +array.prototype.find@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" + integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== dependencies: define-properties "^1.1.3" - es-abstract "^1.13.0" + es-abstract "^1.17.4" array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: version "1.2.3" @@ -8826,6 +8791,14 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -9007,7 +8980,7 @@ chai@^4.1.2: pathval "^1.1.0" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9564,17 +9537,12 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-convert@~0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" - integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= - color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -11703,10 +11671,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz#ef3cdb5d3f0d599d8f9c8b18df2fb63c9793739d" - integrity sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA== +dom-accessibility-api@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" + integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== dom-converter@~0.2: version "0.2.0" @@ -12213,46 +12181,48 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== -enzyme-adapter-react-16@^1.15.2: - version "1.15.2" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" - integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== +enzyme-adapter-react-16@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz#fd677a658d62661ac5afd7f7f541f141f8085901" + integrity sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g== dependencies: - enzyme-adapter-utils "^1.13.0" - enzyme-shallow-equal "^1.0.1" + enzyme-adapter-utils "^1.14.0" + enzyme-shallow-equal "^1.0.4" has "^1.0.3" - object.assign "^4.1.0" - object.values "^1.1.1" + object.assign "^4.1.2" + object.values "^1.1.2" prop-types "^15.7.2" - react-is "^16.12.0" + react-is "^16.13.1" react-test-renderer "^16.0.0-0" semver "^5.7.0" -enzyme-adapter-utils@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" - integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== +enzyme-adapter-utils@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz#afbb0485e8033aa50c744efb5f5711e64fbf1ad0" + integrity sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg== dependencies: - airbnb-prop-types "^2.15.0" - function.prototype.name "^1.1.2" - object.assign "^4.1.0" - object.fromentries "^2.0.2" + airbnb-prop-types "^2.16.0" + function.prototype.name "^1.1.3" + has "^1.0.3" + object.assign "^4.1.2" + object.fromentries "^2.0.3" prop-types "^15.7.2" semver "^5.7.1" -enzyme-shallow-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" - integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== +enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" + integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q== dependencies: has "^1.0.3" - object-is "^1.0.2" + object-is "^1.1.2" -enzyme-to-json@^3.4.4: - version "3.4.4" - resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz#b30726c59091d273521b6568c859e8831e94d00e" - integrity sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA== +enzyme-to-json@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc" + integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg== dependencies: + "@types/cheerio" "^0.22.22" lodash "^4.17.15" react-is "^16.12.0" @@ -12324,57 +12294,27 @@ error-stack-parser@^2.0.4, error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.9.0: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5, es-abstract@^1.18.0-next.2, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.9.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.17.2: - version "1.17.7" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" es-array-method-boxes-properly@^1.0.0: version "1.0.0" @@ -12697,10 +12637,10 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^24.0.2: - version "24.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.2.tgz#4bf0fcdc86289d702a7dacb430b4363482af773b" - integrity sha512-DSBLNpkKDOpUJQkTGSs5sVJWsu0nDyQ2rYxkr0Eh7nrkc5bMUr/dlDbtTj3l8y6UaCVsem6rryF1OZrKnz1S5g== +eslint-plugin-jest@^24.3.4: + version "24.3.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.4.tgz#6d90c3554de0302e879603dd6405474c98849f19" + integrity sha512-3n5oY1+fictanuFkTWPwSlehugBTAgwLnYLFsCllzE3Pl1BwywHl5fL0HFxmMjoQY8xhUDk8uAWc3S4JOHGh3A== dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" @@ -13547,6 +13487,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-console@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/filter-console/-/filter-console-0.1.1.tgz#6242be28982bba7415bcc6db74a79f4a294fa67c" + integrity sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg== + finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -14066,24 +14011,25 @@ function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.0, function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" - integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== +function.prototype.name@^1.1.0, function.prototype.name@^1.1.2, function.prototype.name@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.4.tgz#e4ea839b9d3672ae99d0efd9f38d9191c5eaac83" + integrity sha512-iqy1pIotY/RmhdFZygSSlW0wko2yxkSCKqsuv4pr8QESohpYyG/Z7B/XXvPRKTJS//960rgguE5mSRUsDdaJrQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - functions-have-names "^1.2.0" + es-abstract "^1.18.0-next.2" + functions-have-names "^1.2.2" functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.0.tgz#83da7583e4ea0c9ac5ff530f73394b033e0bf77d" - integrity sha512-zKXyzksTeaCSw5wIX79iCA40YAa6CJMJgNg9wdkU/ERBrIdPSimPICYiLp65lRbSBqtiHql/HZfS2DyI/AH6tQ== +functions-have-names@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" + integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== fuse.js@^3.6.1: version "3.6.1" @@ -14165,6 +14111,15 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" @@ -14946,6 +14901,11 @@ has-ansi@^3.0.0: dependencies: ansi-regex "^3.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-color@~0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" @@ -14973,10 +14933,10 @@ has-glob@^1.0.0: dependencies: is-glob "^3.0.0" -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-unicode@^2.0.0: version "2.0.1" @@ -16033,14 +15993,14 @@ is-arrayish@^0.2.1: integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-arrayish@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd" - integrity sha1-wt/DhquqDD4zxI2z/ocFnmkGXv0= + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== +is-bigint@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" + integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== is-binary-path@^1.0.0: version "1.0.1" @@ -16056,10 +16016,12 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0, is-boolean-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== +is-boolean-object@^1.0.1, is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" is-buffer@^1.0.2, is-buffer@^1.1.0, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" @@ -16071,15 +16033,10 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-ci@^2.0.0: version "2.0.0" @@ -16297,10 +16254,10 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-nil@^1.0.0: version "1.0.1" @@ -16317,7 +16274,7 @@ is-npm@^4.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== -is-number-object@^1.0.3, is-number-object@^1.0.4: +is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== @@ -16431,11 +16388,12 @@ is-redirect@^1.0.0: resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= -is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== +is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1, is-regex@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== dependencies: + call-bind "^1.0.2" has-symbols "^1.0.1" is-regexp@^2.0.0: @@ -16781,13 +16739,13 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" -jest-canvas-mock@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz#45fbc58589c6ce9df50dc90bd8adce747cbdada7" - integrity sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw== +jest-canvas-mock@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.1.tgz#9535d14bc18ccf1493be36ac37dd349928387826" + integrity sha512-5FnSZPrX3Q2ZfsbYNE3wqKR3+XorN8qFzDzB5o0golWgt6EOX1+emBnpOc9IAQ+NXFj8Nzm3h7ZdE/9H0ylBcg== dependencies: cssfontparser "^1.2.1" - parse-color "^1.0.0" + moo-color "^1.0.2" jest-changed-files@^26.6.2: version "26.6.2" @@ -16888,7 +16846,7 @@ jest-diff@^25.2.1: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-diff@^26.6.2: +jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -16916,15 +16874,6 @@ jest-each@^26.6.2: jest-util "^26.6.2" pretty-format "^26.6.2" -jest-environment-jsdom-thirteen@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom-thirteen/-/jest-environment-jsdom-thirteen-1.0.1.tgz#113e3c8aed945dadbc826636fa21139c69567bb5" - integrity sha512-Zi7OuKF7HMLlBvomitd5eKp5Ykc4Wvw0d+i+cpbCaE+7kmvL24SO4ssDmKrT++aANXR4T8+pmoJIlav5gr2peQ== - dependencies: - jest-mock "^24.0.0" - jest-util "^24.0.0" - jsdom "^13.0.0" - jest-environment-jsdom@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" @@ -16965,7 +16914,7 @@ jest-get-type@^26.3.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^26.5.2, jest-haste-map@^26.6.2: +jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== @@ -17067,13 +17016,6 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-mock@^24.0.0, jest-mock@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" - integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== - dependencies: - "@jest/types" "^24.9.0" - jest-mock@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" @@ -17122,7 +17064,7 @@ jest-resolve@^24.9.0: jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-resolve@^26.5.2, jest-resolve@^26.6.2: +jest-resolve@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== @@ -17203,13 +17145,13 @@ jest-serializer@^26.6.2: "@types/node" "*" graceful-fs "^4.2.4" -jest-silent-reporter@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.2.1.tgz#554dd62b800989cdbcfba22bf30a1c0db6ad289c" - integrity sha512-nEO3oOFHtEXFjlRCbJOlvEWA7ZHyyyvMsU4WHuAhinYBOI4PiX1EIbsZfQZ/cxHcYliHBU9zY8bPxMPdBGksYw== +jest-silent-reporter@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.5.0.tgz#5fd8ccd61665227e3bf19d908b7350719d06ff38" + integrity sha512-epdLt8Oj0a1AyRiR6F8zx/1SVT1Mi7VU3y4wB2uOBHs/ohIquC7v2eeja7UN54uRPyHInIKWdL+RdG228n5pJQ== dependencies: - chalk "^2.3.1" - jest-util "^24.0.0" + chalk "^4.0.0" + jest-util "^26.0.0" jest-snapshot@^24.1.0: version "24.9.0" @@ -17266,32 +17208,14 @@ jest-specific-snapshot@^4.0.0: dependencies: jest-snapshot "^26.3.0" -jest-styled-components@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.2.tgz#b7711871ea74a04491b12bad123fa35cc65a2a80" - integrity sha512-i1Qke8Jfgx0Why31q74ohVj9S2FmMLUE8bNRSoK4DgiurKkXG6HC4NPhcOLAz6VpVd9wXkPn81hOt4aAQedqsA== +jest-styled-components@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.3.tgz#cc0b031f910484e68f175568682f3969ff774b2c" + integrity sha512-jj9sWyshehUnB0P9WFUaq9Bkh6RKYO8aD8lf3gUrXRwg/MRddTFk7U9D9pC4IAI3v9fbz4vmrMxwaecTpG8NKA== dependencies: css "^2.2.4" -jest-util@^24.0.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" - integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== - dependencies: - "@jest/console" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/source-map" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - callsites "^3.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.15" - is-ci "^2.0.0" - mkdirp "^0.5.1" - slash "^2.0.0" - source-map "^0.6.0" - -jest-util@^26.5.2, jest-util@^26.6.2: +jest-util@^26.0.0, jest-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== @@ -17328,10 +17252,10 @@ jest-watcher@^26.6.2: jest-util "^26.6.2" string-length "^4.0.1" -jest-when@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/jest-when/-/jest-when-2.7.2.tgz#b7b4225e8882bd84a1cfd09216b2c63d22f892bd" - integrity sha512-GuVzimG0wW18A5JlYwhHrvuwmWRAQpsnilRVdJktvrZX5V0++al1f/iwITE7+Cud8Rbw/U2eka4tyy7kvxIWnw== +jest-when@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jest-when/-/jest-when-3.2.1.tgz#69b58ff641a399a0f2db5bfee6d8dd40cd065eb8" + integrity sha512-7OuFR5f2AdDPoRs/uk99dEWI+Isc2SFThugPjVUZgLLhWqeGr64rCFuuYcxVXQKwBmF3GG/MCS6zcKR9H86qiw== dependencies: bunyan "^1.8.12" expect "^24.8.0" @@ -17344,7 +17268,7 @@ jest-worker@^25.4.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^26.2.1, jest-worker@^26.3.0, jest-worker@^26.5.0, jest-worker@^26.6.2: +jest-worker@^26.2.1, jest-worker@^26.3.0, jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== @@ -17495,7 +17419,7 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@13.1.0, jsdom@^13.0.0: +jsdom@13.1.0: version "13.1.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-13.1.0.tgz#fa7356f0cc8111d0f1077cb7800d06f22f1d66c7" integrity sha512-C2Kp0qNuopw0smXFaHeayvharqF3kkcNqlcIlSX71+3XrsOFwkEPLt/9f5JksMmaul2JZYIQuY+WTpqHpQQcLg== @@ -19591,6 +19515,13 @@ monocle-ts@^1.0.0: resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-1.7.1.tgz#03a615938aa90983a4fa29749969d30f72d80ba1" integrity sha512-X9OzpOyd/R83sYex8NYpJjUzi/MLQMvGNVfxDYiIvs+QMXMEUDwR61MQoARFN10Cqz5h/mbFSPnIQNUIGhYd2Q== +moo-color@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.2.tgz#837c40758d2d58763825d1359a84e330531eca64" + integrity sha512-5iXz5n9LWQzx/C2WesGFfpE6RLamzdHwsn3KpfzShwbfIqs7stnoEpaNErf/7+3mbxwZ4s8Foq7I0tPxw7BWHg== + dependencies: + color-name "^1.1.4" + moo@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" @@ -20397,15 +20328,10 @@ object-identity-map@^1.0.2: dependencies: object.entries "^1.1.0" -object-inspect@^1.6.0, object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.6.0, object-inspect@^1.7.0, object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== object-inspect@~1.6.0: version "1.6.0" @@ -20420,7 +20346,7 @@ object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2: define-properties "^1.1.3" es-abstract "^1.17.5" -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -20439,23 +20365,13 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" @@ -20478,14 +20394,14 @@ object.entries@^1.0.4, object.entries@^1.1.0, object.entries@^1.1.1, object.entr es-abstract "^1.17.5" has "^1.0.3" -"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" - integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== +"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2, object.fromentries@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" has "^1.0.3" object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: @@ -20519,14 +20435,14 @@ object.reduce@^1.0.0: for-own "^1.0.0" make-iterator "^1.0.0" -object.values@^1.1.0, object.values@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== +object.values@^1.1.0, object.values@^1.1.1, object.values@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" + integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" has "^1.0.3" objectorarray@^1.0.4: @@ -21022,13 +20938,6 @@ parse-bmfont-xml@^1.1.4: xml-parse-from-string "^1.0.0" xml2js "^0.4.5" -parse-color@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" - integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= - dependencies: - color-convert "~0.5.0" - parse-data-uri@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9" @@ -22054,7 +21963,7 @@ pretty-format@^25.2.1, pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.4.0, pretty-format@^26.4.2, pretty-format@^26.6.2: +pretty-format@^26.0.0, pretty-format@^26.4.0, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== @@ -22802,6 +22711,13 @@ react-element-to-jsx-string@^14.3.1: "@base2/pretty-print-object" "1.0.0" is-plain-object "3.0.0" +react-error-boundary@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.1.tgz#932c5ca5cbab8ec4fe37fd7b415aa5c3a47597e7" + integrity sha512-W3xCd9zXnanqrTUeViceufD3mIW8Ut29BUD+S2f0eO2XCOU8b6UrJfY46RDGe5lxCJzfe4j0yvIfh0RbTZhKJw== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@^6.0.9: version "6.0.9" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" @@ -22905,7 +22821,7 @@ react-intl@^2.8.0: intl-relativeformat "^2.1.0" invariant "^2.1.1" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -25852,21 +25768,21 @@ string.prototype.trim@~1.1.2: es-abstract "^1.5.0" function-bind "^1.0.2" -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1: version "1.1.1" @@ -27346,6 +27262,16 @@ umd@^3.0.0: resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== +unbox-primitive@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unbzip2-stream@^1.3.3: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -28069,15 +27995,6 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1, v8-compile-cache@^2.2.0: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== -v8-to-istanbul@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz#7ef0e32faa10f841fe4c1b0f8de96ed067c0be1e" - integrity sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - v8-to-istanbul@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" @@ -28993,16 +28910,16 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^6.1.0" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== +which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-collection@^1.0.1: version "1.0.1" From 4fbb48daea8e29fe22bf57510f65443036489679 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 21 Apr 2021 20:50:05 +0100 Subject: [PATCH 03/33] chore(NA): chore(NA): moving @kbn/std into bazel (#97771) --- .../monorepo-packages.asciidoc | 1 + package.json | 2 +- packages/BUILD.bazel | 1 + packages/kbn-cli-dev-mode/package.json | 1 - packages/kbn-config/package.json | 3 +- packages/kbn-logging/package.json | 3 - packages/kbn-optimizer/package.json | 1 - packages/kbn-server-http-tools/package.json | 3 +- packages/kbn-std/BUILD.bazel | 85 +++++++++++++++++++ packages/kbn-std/package.json | 9 +- packages/kbn-std/tsconfig.json | 7 +- packages/kbn-utility-types/BUILD.bazel | 5 +- yarn.lock | 2 +- 13 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 packages/kbn-std/BUILD.bazel diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 9564087dabefe..610d78bacccd4 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -65,6 +65,7 @@ yarn kbn watch-bazel - @kbn/apm-utils - @kbn/babel-preset - @kbn/config-schema +- @kbn/std - @kbn/tinymath - @kbn/utility-types diff --git a/package.json b/package.json index dc2eba88139bb..9981dc04cb3e4 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "@kbn/monaco": "link:packages/kbn-monaco", "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", - "@kbn/std": "link:packages/kbn-std", + "@kbn/std": "link:bazel-bin/packages/kbn-std/npm_module", "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath/npm_module", "@kbn/ui-framework": "link:packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index e1a85e926f049..552eed64d418c 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -7,6 +7,7 @@ filegroup( "//packages/kbn-apm-utils:build", "//packages/kbn-babel-preset:build", "//packages/kbn-config-schema:build", + "//packages/kbn-std:build", "//packages/kbn-tinymath:build", "//packages/kbn-utility-types:build", ], diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json index 1ea319ef3601c..2ffa09d7e1604 100644 --- a/packages/kbn-cli-dev-mode/package.json +++ b/packages/kbn-cli-dev-mode/package.json @@ -18,7 +18,6 @@ "@kbn/logging": "link:../kbn-logging", "@kbn/server-http-tools": "link:../kbn-server-http-tools", "@kbn/optimizer": "link:../kbn-optimizer", - "@kbn/std": "link:../kbn-std", "@kbn/dev-utils": "link:../kbn-dev-utils", "@kbn/utils": "link:../kbn-utils" } diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json index 8093b6ac0d211..9bf491e300871 100644 --- a/packages/kbn-config/package.json +++ b/packages/kbn-config/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set", - "@kbn/logging": "link:../kbn-logging", - "@kbn/std": "link:../kbn-std" + "@kbn/logging": "link:../kbn-logging" }, "devDependencies": { "@kbn/dev-utils": "link:../kbn-dev-utils", diff --git a/packages/kbn-logging/package.json b/packages/kbn-logging/package.json index c7db148c75a2a..596eda1fe625a 100644 --- a/packages/kbn-logging/package.json +++ b/packages/kbn-logging/package.json @@ -9,8 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/std": "link:../kbn-std" } } \ No newline at end of file diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 3c14d98755a32..423bba0fd8c7a 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -13,7 +13,6 @@ "dependencies": { "@kbn/config": "link:../kbn-config", "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/std": "link:../kbn-std", "@kbn/ui-shared-deps": "link:../kbn-ui-shared-deps" } } \ No newline at end of file diff --git a/packages/kbn-server-http-tools/package.json b/packages/kbn-server-http-tools/package.json index 24f8f8d67dfd7..5a1bb0d5b536a 100644 --- a/packages/kbn-server-http-tools/package.json +++ b/packages/kbn-server-http-tools/package.json @@ -11,8 +11,7 @@ "kbn:watch": "yarn build --watch" }, "dependencies": { - "@kbn/crypto": "link:../kbn-crypto", - "@kbn/std": "link:../kbn-std" + "@kbn/crypto": "link:../kbn-crypto" }, "devDependencies": { "@kbn/utility-types": "link:../kbn-utility-types" diff --git a/packages/kbn-std/BUILD.bazel b/packages/kbn-std/BUILD.bazel new file mode 100644 index 0000000000000..82520be97df1f --- /dev/null +++ b/packages/kbn-std/BUILD.bazel @@ -0,0 +1,85 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-std" +PKG_REQUIRE_NAME = "@kbn/std" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = ["**/*.test.*"], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/kbn-utility-types", + "@npm//lodash", + "@npm//query-string", + "@npm//rxjs", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/lodash", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-std/package.json b/packages/kbn-std/package.json index b914356d99249..d88422ec1aa81 100644 --- a/packages/kbn-std/package.json +++ b/packages/kbn-std/package.json @@ -4,12 +4,5 @@ "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build" - }, - "devDependencies": { - "@kbn/utility-types": "link:../kbn-utility-types" - } + "private": true } \ No newline at end of file diff --git a/packages/kbn-std/tsconfig.json b/packages/kbn-std/tsconfig.json index d2ed46dcad6f8..dec2d2df64086 100644 --- a/packages/kbn-std/tsconfig.json +++ b/packages/kbn-std/tsconfig.json @@ -1,12 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, - "declarationDir": "./target", + "incremental": true, "outDir": "./target", "stripInternal": true, "declaration": true, "declarationMap": true, + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-std/src", "types": [ @@ -16,8 +16,5 @@ }, "include": [ "./src/**/*.ts" - ], - "exclude": [ - "**/__fixture__/**/*" ] } diff --git a/packages/kbn-utility-types/BUILD.bazel b/packages/kbn-utility-types/BUILD.bazel index e22ba38b24a48..1a02f94a88f4a 100644 --- a/packages/kbn-utility-types/BUILD.bazel +++ b/packages/kbn-utility-types/BUILD.bazel @@ -57,15 +57,14 @@ ts_project( js_library( name = PKG_BASE_NAME, - srcs = [], + srcs = NPM_MODULE_EXTRA_FILES, deps = [":tsc"] + DEPS, - package_name = PKG_REQUIRE_NAME, + package_name = "@kbn/utility-types", visibility = ["//visibility:public"], ) pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/yarn.lock b/yarn.lock index f6ce8d8093607..f4d7684174967 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2685,7 +2685,7 @@ version "0.0.0" uid "" -"@kbn/std@link:packages/kbn-std": +"@kbn/std@link:bazel-bin/packages/kbn-std/npm_module": version "0.0.0" uid "" From 45255425f492321ec178a85d65173ca5257b5e8a Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 21 Apr 2021 15:52:02 -0400 Subject: [PATCH 04/33] Add the more explicit & actionable text listed in ticket (#97857) ## Summary closes #92591 Create a new `HostedAgentPolicyRestrictionRelatedError` which ensures the additional text from #92591 is included in all the appropriate places, but only specified once. #### Some current examples * `Cannot update integrations of hosted agent policy ${id}` * `Cannot remove integrations of hosted agent policy ${id}` * `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id}` #### In this PR are now * `Cannot update integrations of hosted agent policy ${id} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` * `Cannot remove integrations of hosted agent policy ${id} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` * `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- x-pack/plugins/fleet/server/errors/index.ts | 9 +++++++-- .../fleet/server/services/agent_policy.ts | 16 ++++++++-------- .../server/services/agents/reassign.test.ts | 8 ++++---- .../fleet/server/services/agents/reassign.ts | 6 +++--- .../server/services/agents/unenroll.test.ts | 6 +++--- .../fleet/server/services/agents/unenroll.ts | 4 ++-- .../fleet/server/services/agents/upgrade.ts | 10 +++++++--- .../fleet/server/services/package_policy.ts | 8 ++++++-- .../apis/agents/reassign.ts | 6 ++++-- .../apis/agents/unenroll.ts | 6 ++++-- .../fleet_api_integration/apis/agents/upgrade.ts | 6 +++++- 11 files changed, 53 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 793a349f730f3..8d75726fbe2de 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -43,8 +43,13 @@ export class PackageOperationNotSupportedError extends IngestManagerError {} export class FleetAdminUserInvalidError extends IngestManagerError {} export class ConcurrentInstallOperationError extends IngestManagerError {} export class AgentReassignmentError extends IngestManagerError {} -export class AgentUnenrollmentError extends IngestManagerError {} -export class AgentPolicyDeletionError extends IngestManagerError {} +export class HostedAgentPolicyRestrictionRelatedError extends IngestManagerError { + constructor(message = 'Cannot perform that action') { + super( + `${message} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` + ); + } +} export class FleetSetupError extends IngestManagerError {} export class GenerateServiceTokenError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index deb2da8dee553..0d1c5c4dd3143 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -46,11 +46,7 @@ import type { Installation, Output, } from '../../common'; -import { - AgentPolicyNameExistsError, - AgentPolicyDeletionError, - IngestManagerError, -} from '../errors'; +import { AgentPolicyNameExistsError, HostedAgentPolicyRestrictionRelatedError } from '../errors'; import { getPackageInfo } from './epm/packages'; import { getAgentsByKuery } from './agents'; @@ -476,7 +472,9 @@ class AgentPolicyService { } if (oldAgentPolicy.is_managed && !options?.force) { - throw new IngestManagerError(`Cannot update integrations of hosted agent policy ${id}`); + throw new HostedAgentPolicyRestrictionRelatedError( + `Cannot update integrations of hosted agent policy ${id}` + ); } return await this._update( @@ -507,7 +505,9 @@ class AgentPolicyService { } if (oldAgentPolicy.is_managed && !options?.force) { - throw new IngestManagerError(`Cannot remove integrations of hosted agent policy ${id}`); + throw new HostedAgentPolicyRestrictionRelatedError( + `Cannot remove integrations of hosted agent policy ${id}` + ); } return await this._update( @@ -550,7 +550,7 @@ class AgentPolicyService { } if (agentPolicy.is_managed) { - throw new AgentPolicyDeletionError(`Cannot delete hosted agent policy ${id}`); + throw new HostedAgentPolicyRestrictionRelatedError(`Cannot delete hosted agent policy ${id}`); } const { diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index 4dfc29df8c398..63085b7729c4b 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import type { SavedObject } from 'kibana/server'; import type { AgentPolicy } from '../../types'; -import { AgentReassignmentError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { reassignAgent, reassignAgents } from './reassign'; @@ -54,7 +54,7 @@ describe('reassignAgent (singular)', () => { const { soClient, esClient } = createClientsMock(); await expect( reassignAgent(soClient, esClient, agentInRegularDoc._id, hostedAgentPolicySO.id) - ).rejects.toThrowError(AgentReassignmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); @@ -64,13 +64,13 @@ describe('reassignAgent (singular)', () => { const { soClient, esClient } = createClientsMock(); await expect( reassignAgent(soClient, esClient, agentInHostedDoc._id, regularAgentPolicySO.id) - ).rejects.toThrowError(AgentReassignmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); await expect( reassignAgent(soClient, esClient, agentInHostedDoc._id, hostedAgentPolicySO.id) - ).rejects.toThrowError(AgentReassignmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); }); diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 4c95d19e2f13a..e72f441afd031 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -10,7 +10,7 @@ import Boom from '@hapi/boom'; import type { Agent, BulkActionResult } from '../../types'; import { agentPolicyService } from '../agent_policy'; -import { AgentReassignmentError } from '../../errors'; +import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { getAgentDocuments, @@ -56,14 +56,14 @@ export async function reassignAgentIsAllowed( ) { const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { - throw new AgentReassignmentError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot reassign an agent from hosted agent policy ${agentPolicy.id}` ); } const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); if (newAgentPolicy?.is_managed) { - throw new AgentReassignmentError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id}` ); } diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index 24a3dea3bcb91..33f12dc52dc00 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import type { SavedObject } from 'kibana/server'; import type { AgentPolicy } from '../../types'; -import { AgentUnenrollmentError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { unenrollAgent, unenrollAgents } from './unenroll'; @@ -49,7 +49,7 @@ describe('unenrollAgent (singular)', () => { it('cannot unenroll from hosted agent policy by default', async () => { const { soClient, esClient } = createClientMock(); await expect(unenrollAgent(soClient, esClient, agentInHostedDoc._id)).rejects.toThrowError( - AgentUnenrollmentError + HostedAgentPolicyRestrictionRelatedError ); // does not call ES update expect(esClient.update).toBeCalledTimes(0); @@ -59,7 +59,7 @@ describe('unenrollAgent (singular)', () => { const { soClient, esClient } = createClientMock(); await expect( unenrollAgent(soClient, esClient, agentInHostedDoc._id, { revoke: true }) - ).rejects.toThrowError(AgentUnenrollmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); }); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index fc1f80fe7521b..4d062e8bd5368 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -9,7 +9,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s import type { Agent, BulkActionResult } from '../../types'; import * as APIKeyService from '../api_keys'; -import { AgentUnenrollmentError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { createAgentAction, bulkCreateAgentActions } from './actions'; import type { GetAgentsOptions } from './crud'; @@ -28,7 +28,7 @@ async function unenrollAgentIsAllowed( ) { const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { - throw new AgentUnenrollmentError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot unenroll ${agentId} from a hosted agent policy ${agentPolicy.id}` ); } diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 61e785828bf23..988d3c63223f4 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -10,7 +10,11 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s import type { Agent, AgentAction, AgentActionSOAttributes, BulkActionResult } from '../../types'; import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../constants'; import { agentPolicyService } from '../../services'; -import { AgentReassignmentError, IngestManagerError } from '../../errors'; +import { + AgentReassignmentError, + HostedAgentPolicyRestrictionRelatedError, + IngestManagerError, +} from '../../errors'; import { isAgentUpgradeable } from '../../../common/services'; import { appContextService } from '../app_context'; @@ -46,7 +50,7 @@ export async function sendUpgradeAgentAction({ const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { - throw new IngestManagerError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot upgrade agent ${agentId} in hosted agent policy ${agentPolicy.id}` ); } @@ -142,7 +146,7 @@ export async function sendUpgradeAgentsActions( } if (!options.force && isHostedAgent(agent)) { - throw new IngestManagerError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot upgrade agent in hosted agent policy ${agent.policy_id}` ); } diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 7c009299a3de3..234fa4df51688 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -30,7 +30,11 @@ import type { ListResult, } from '../../common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; -import { IngestManagerError, ingestErrorToResponseOptions } from '../errors'; +import { + HostedAgentPolicyRestrictionRelatedError, + IngestManagerError, + ingestErrorToResponseOptions, +} from '../errors'; import { NewPackagePolicySchema, UpdatePackagePolicySchema } from '../types'; import type { NewPackagePolicy, @@ -75,7 +79,7 @@ class PackagePolicyService { throw new Error('Agent policy not found'); } if (parentAgentPolicy.is_managed && !options?.force) { - throw new IngestManagerError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot add integrations to hosted agent policy ${parentAgentPolicy.id}` ); } diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index ad3c224bb9236..ac5aabc5c5084 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -157,7 +157,8 @@ export default function (providerContext: FtrProviderContext) { expect(body).to.eql({ agent2: { success: false, - error: 'Cannot reassign an agent from hosted agent policy policy1', + error: + 'Cannot reassign an agent from hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, INVALID_ID: { success: false, @@ -165,7 +166,8 @@ export default function (providerContext: FtrProviderContext) { }, agent3: { success: false, - error: 'Cannot reassign an agent from hosted agent policy policy1', + error: + 'Cannot reassign an agent from hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index f0e41d75136c3..df213e82bac7c 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -138,11 +138,13 @@ export default function (providerContext: FtrProviderContext) { expect(unenrolledBody).to.eql({ agent2: { success: false, - error: 'Cannot unenroll agent2 from a hosted agent policy policy1', + error: + 'Cannot unenroll agent2 from a hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, agent3: { success: false, - error: 'Cannot unenroll agent3 from a hosted agent policy policy1', + error: + 'Cannot unenroll agent3 from a hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, }); // but agents are still enrolled diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index 142c360e9232a..b692699182cac 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -593,7 +593,11 @@ export default function (providerContext: FtrProviderContext) { .expect(200); expect(body).to.eql({ - agent1: { success: false, error: 'Cannot upgrade agent in hosted agent policy policy1' }, + agent1: { + success: false, + error: + 'Cannot upgrade agent in hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', + }, agent2: { success: true }, }); From 688bb6d3e229952e8af30862557bc95cb9243a1b Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 13:03:59 -0700 Subject: [PATCH 05/33] remove jest-environment-jsdom-thirteen from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 9981dc04cb3e4..992433e17e6c1 100644 --- a/package.json +++ b/package.json @@ -732,7 +732,6 @@ "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", "jest-environment-jsdom": "^26.6.2", - "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", "jest-silent-reporter": "^0.5.0", "jest-snapshot": "^26.6.2", From 1fb00900e1c2c229675d407ac810c1fffc797510 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:13:15 -0400 Subject: [PATCH 06/33] [Security Solution][Detections] Fix flaky threshold API tests (#97768) * Explicitly refreshes signals index for threshold api tests to prevent flakiness * Unskip test suite --- .../tests/generating_signals.ts | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index 7d69e006666cd..4ae949d0cba86 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -20,6 +20,7 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, + getOpenSignals, getRuleForSignalTesting, getSignalsByIds, getSignalsByRuleIds, @@ -39,9 +40,9 @@ export const ID = 'BhbXBmkBR346wHgn4PeZ'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/97584 - describe.skip('Generating signals from source indexes', () => { + describe('Generating signals from source indexes', () => { beforeEach(async () => { await createSignalsIndex(supertest); }); @@ -728,9 +729,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -753,9 +753,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -778,9 +777,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const signal = signalsOpen.hits.hits[0]; expect(signal._source.signal.threshold_result).eql({ @@ -814,9 +812,8 @@ export default ({ getService }: FtrProviderContext) => { value: 22, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -833,9 +830,8 @@ export default ({ getService }: FtrProviderContext) => { value: 21, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const signal = signalsOpen.hits.hits[0]; expect(signal._source.signal.threshold_result).eql({ From e76987e13c8972086b5ad198374b1cb5278353b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:47:03 -0400 Subject: [PATCH 07/33] [APM] add comparison to Instances latency distribution (#97710) * adding comparion data to chart * fixing api test * addressing comments * refactoring --- ...ice_overview_instances_chart_and_table.tsx | 68 ++++++----- .../index.tsx | 2 +- .../index.tsx | 56 +++++++-- x-pack/plugins/apm/server/routes/services.ts | 44 +++++-- .../instances_main_statistics.ts | 109 ++++++++++++++++-- 5 files changed, 215 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 8305b5a0dde3b..8513e0835d373 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -33,15 +33,16 @@ export interface MainStatsServiceInstanceItem { cpuUsage: number; memoryUsage: number; } +type ApiResponseMainStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; +type ApiResponseDetailedStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; const INITIAL_STATE_MAIN_STATS = { - mainStatsItems: [] as MainStatsServiceInstanceItem[], - mainStatsRequestId: undefined, - mainStatsItemCount: 0, + currentPeriodItems: [] as ApiResponseMainStats['currentPeriod'], + previousPeriodItems: [] as ApiResponseMainStats['previousPeriod'], + requestId: undefined, + currentPeriodItemsCount: 0, }; -type ApiResponseDetailedStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; - const INITIAL_STATE_DETAILED_STATISTICS: ApiResponseDetailedStats = { currentPeriod: {}, previousPeriod: {}, @@ -117,28 +118,17 @@ export function ServiceOverviewInstancesChartAndTable({ start, end, transactionType, + comparisonStart, + comparisonEnd, }, }, }).then((response) => { - const mainStatsItems = orderBy( - // need top-level sortable fields for the managed table - response.serviceInstances.map((item) => ({ - ...item, - latency: item.latency ?? 0, - throughput: item.throughput ?? 0, - errorRate: item.errorRate ?? 0, - cpuUsage: item.cpuUsage ?? 0, - memoryUsage: item.memoryUsage ?? 0, - })), - field, - direction - ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); - return { // Everytime the main statistics is refetched, updates the requestId making the detailed API to be refetched. - mainStatsRequestId: uuid(), - mainStatsItems, - mainStatsItemCount: response.serviceInstances.length, + requestId: uuid(), + currentPeriodItems: response.currentPeriod, + currentPeriodItemsCount: response.currentPeriod.length, + previousPeriodItems: response.previousPeriod, }; }); }, @@ -162,11 +152,26 @@ export function ServiceOverviewInstancesChartAndTable({ ); const { - mainStatsItems, - mainStatsRequestId, - mainStatsItemCount, + currentPeriodItems, + previousPeriodItems, + requestId, + currentPeriodItemsCount, } = mainStatsData; + const currentPeriodOrderedItems = orderBy( + // need top-level sortable fields for the managed table + currentPeriodItems.map((item) => ({ + ...item, + latency: item.latency ?? 0, + throughput: item.throughput ?? 0, + errorRate: item.errorRate ?? 0, + cpuUsage: item.cpuUsage ?? 0, + memoryUsage: item.memoryUsage ?? 0, + })), + field, + direction + ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); + const { data: detailedStatsData = INITIAL_STATE_DETAILED_STATISTICS, status: detailedStatsStatus, @@ -177,7 +182,7 @@ export function ServiceOverviewInstancesChartAndTable({ !end || !transactionType || !latencyAggregationType || - !mainStatsItemCount + !currentPeriodItemsCount ) { return; } @@ -198,7 +203,7 @@ export function ServiceOverviewInstancesChartAndTable({ numBuckets: 20, transactionType, serviceNodeIds: JSON.stringify( - mainStatsItems.map((item) => item.serviceNodeName) + currentPeriodOrderedItems.map((item) => item.serviceNodeName) ), comparisonStart, comparisonEnd, @@ -208,7 +213,7 @@ export function ServiceOverviewInstancesChartAndTable({ }, // only fetches detailed statistics when requestId is invalidated by main statistics api call // eslint-disable-next-line react-hooks/exhaustive-deps - [mainStatsRequestId], + [requestId], { preservePreviousData: false } ); @@ -217,16 +222,17 @@ export function ServiceOverviewInstancesChartAndTable({

{i18n.translate('xpack.apm.serviceOverview.instancesTableTitle', { - defaultMessage: 'All instances', + defaultMessage: 'Instances', })}

diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx index 394d5b5410d41..ce4f36ced7903 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx @@ -30,33 +30,32 @@ import { } from '../../../../../common/utils/formatters'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; -import { MainStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import * as urlHelpers from '../../Links/url_helpers'; import { ChartContainer } from '../chart_container'; import { getResponseTimeTickFormatter } from '../transaction_charts/helper'; import { CustomTooltip } from './custom_tooltip'; +type ApiResponseMainStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + export interface InstancesLatencyDistributionChartProps { height: number; - items?: MainStatsServiceInstanceItem[]; + items?: ApiResponseMainStats['currentPeriod']; status: FETCH_STATUS; + comparisonItems?: ApiResponseMainStats['previousPeriod']; } export function InstancesLatencyDistributionChart({ height, items = [], status, + comparisonItems = [], }: InstancesLatencyDistributionChartProps) { const history = useHistory(); const hasData = items.length > 0; const theme = useTheme(); - const chartTheme = { - ...useChartTheme(), - bubbleSeriesStyle: { - point: { strokeWidth: 0, fill: theme.eui.euiColorVis1, radius: 4 }, - }, - }; + const chartTheme = useChartTheme(); const maxLatency = Math.max(...items.map((item) => item.latency ?? 0)); const latencyFormatter = getDurationFormatter(maxLatency); @@ -96,7 +95,13 @@ export function InstancesLatencyDistributionChart({ // there's just a single instance) they'll show along the origin. Make sure // the x-axis domain is [0, maxThroughput]. const maxThroughput = Math.max(...items.map((item) => item.throughput ?? 0)); - const xDomain = { min: 0, max: maxThroughput }; + const maxComparisonThroughput = Math.max( + ...comparisonItems.map((item) => item.throughput ?? 0) + ); + const xDomain = { + min: 0, + max: Math.max(maxThroughput, maxComparisonThroughput), + }; return ( @@ -118,7 +123,7 @@ export function InstancesLatencyDistributionChart({ xDomain={xDomain} /> item.latency]} yScaleType={ScaleType.Linear} + bubbleSeriesStyle={{ + point: { + strokeWidth: 0, + radius: 4, + fill: theme.eui.euiColorVis0, + }, + }} /> + + {!!comparisonItems.length && ( + item.throughput} + xScaleType={ScaleType.Linear} + yAccessors={[(item) => item.latency]} + yScaleType={ScaleType.Linear} + color={theme.eui.euiColorMediumShade} + bubbleSeriesStyle={{ + point: { + shape: 'square', + radius: 4, + fill: theme.eui.euiColorLightestShade, + stroke: theme.eui.euiColorMediumShade, + strokeWidth: 2, + }, + }} + /> + )} { describe('fetching java data', () => { @@ -72,11 +75,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns a service node item', () => { - expect(response.body.serviceInstances.length).to.be.greaterThan(0); + expect(response.body.currentPeriod.length).to.be.greaterThan(0); }); it('returns statistics for each service node', () => { - const item = response.body.serviceInstances[0]; + const item = response.body.currentPeriod[0]; expect(isFiniteNumber(item.cpuUsage)).to.be(true); expect(isFiniteNumber(item.memoryUsage)).to.be(true); @@ -86,7 +89,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right data', () => { - const items = sortBy(response.body.serviceInstances, 'serviceNodeName'); + const items = sortBy(response.body.currentPeriod, 'serviceNodeName'); const serviceNodeNames = items.map((item) => item.serviceNodeName); @@ -141,7 +144,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns statistics for each service node', () => { - const item = response.body.serviceInstances[0]; + const item = response.body.currentPeriod[0]; expect(isFiniteNumber(item.cpuUsage)).to.be(true); expect(isFiniteNumber(item.memoryUsage)).to.be(true); @@ -151,7 +154,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right data', () => { - const items = sortBy(response.body.serviceInstances, 'serviceNodeName'); + const items = sortBy(response.body.currentPeriod, 'serviceNodeName'); const serviceNodeNames = items.map((item) => item.serviceNodeName); @@ -181,4 +184,90 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } ); + + registry.when( + 'Service overview instances main statistics when data is loaded with comparison', + { config: 'basic', archives: [archiveName] }, + () => { + describe('fetching java data', () => { + let response: { + body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`>; + }; + + beforeEach(async () => { + response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + params: { + path: { serviceName: 'opbeans-java' }, + query: { + latencyAggregationType: LatencyAggregationType.avg, + transactionType: 'request', + start: moment(end).subtract(15, 'minutes').toISOString(), + end, + comparisonStart: start, + comparisonEnd: moment(start).add(15, 'minutes').toISOString(), + }, + }, + }); + }); + + it('returns a service node item', () => { + expect(response.body.currentPeriod.length).to.be.greaterThan(0); + expect(response.body.previousPeriod.length).to.be.greaterThan(0); + }); + + it('returns statistics for each service node', () => { + const currentItem = response.body.currentPeriod[0]; + + expect(isFiniteNumber(currentItem.cpuUsage)).to.be(true); + expect(isFiniteNumber(currentItem.memoryUsage)).to.be(true); + expect(isFiniteNumber(currentItem.errorRate)).to.be(true); + expect(isFiniteNumber(currentItem.throughput)).to.be(true); + expect(isFiniteNumber(currentItem.latency)).to.be(true); + + const previousItem = response.body.previousPeriod[0]; + + expect(isFiniteNumber(previousItem.cpuUsage)).to.be(true); + expect(isFiniteNumber(previousItem.memoryUsage)).to.be(true); + expect(isFiniteNumber(previousItem.errorRate)).to.be(true); + expect(isFiniteNumber(previousItem.throughput)).to.be(true); + expect(isFiniteNumber(previousItem.latency)).to.be(true); + }); + + it('returns the right data', () => { + const items = sortBy(response.body.previousPeriod, 'serviceNodeName'); + + const serviceNodeNames = items.map((item) => item.serviceNodeName); + + expectSnapshot(items.length).toMatchInline(`1`); + + expectSnapshot(serviceNodeNames).toMatchInline(` + Array [ + "02950c4c5fbb0fda1cc98c47bf4024b473a8a17629db6530d95dcee68bd54c6c", + ] + `); + + const item = items[0]; + + const values = pick(item, [ + 'cpuUsage', + 'memoryUsage', + 'errorRate', + 'throughput', + 'latency', + ]); + + expectSnapshot(values).toMatchInline(` + Object { + "cpuUsage": 0.0120666666666667, + "errorRate": 0.111111111111111, + "latency": 379742.555555556, + "memoryUsage": 0.939879608154297, + "throughput": 3, + } + `); + }); + }); + } + ); } From 78c0c6eb8344d30fbf4f8d41e0cdc7149c765e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:51:04 -0400 Subject: [PATCH 08/33] [APM] Adding comparison data to the dependencies table (#97843) * adding comparison to dependencies api * fixing client * fixing client * changing client --- .../offset_previous_period_coordinate.test.ts | 0 .../offset_previous_period_coordinate.ts | 0 .../index.tsx | 134 ++++++++++++++++-- .../get_columns.tsx | 2 +- ...service_error_group_detailed_statistics.ts | 2 +- .../detailed_statistics.ts | 2 +- ...e_transaction_group_detailed_statistics.ts | 2 +- .../lib/transaction_groups/get_error_rate.ts | 2 +- .../transactions/get_latency_charts/index.ts | 2 +- x-pack/plugins/apm/server/routes/services.ts | 2 +- 10 files changed, 128 insertions(+), 20 deletions(-) rename x-pack/plugins/apm/{server => common}/utils/offset_previous_period_coordinate.test.ts (100%) rename x-pack/plugins/apm/{server => common}/utils/offset_previous_period_coordinate.ts (100%) diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts b/x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.test.ts similarity index 100% rename from x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts rename to x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.test.ts diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts b/x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.ts similarity index 100% rename from x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts rename to x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 4ff42b151dc8e..55da021e04687 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -13,7 +13,10 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { keyBy } from 'lodash'; import React from 'react'; +import { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate'; +import { Coordinate } from '../../../../../typings/timeseries'; import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values'; import { asMillisecondDuration, @@ -32,6 +35,7 @@ import { ServiceMapLink } from '../../../shared/Links/apm/ServiceMapLink'; import { ServiceOverviewLink } from '../../../shared/Links/apm/service_overview_link'; import { SpanIcon } from '../../../shared/span_icon'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; +import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; @@ -39,12 +43,68 @@ interface Props { serviceName: string; } +type ServiceDependencyPeriods = ServiceDependencyItem & { + latency: { previousPeriodTimeseries?: Coordinate[] }; + throughput: { previousPeriodTimeseries?: Coordinate[] }; + errorRate: { previousPeriodTimeseries?: Coordinate[] }; + previousPeriodImpact?: number; +}; + +function mergeCurrentAndPreviousPeriods({ + currentPeriod = [], + previousPeriod = [], +}: { + currentPeriod?: ServiceDependencyItem[]; + previousPeriod?: ServiceDependencyItem[]; +}): ServiceDependencyPeriods[] { + const previousPeriodMap = keyBy(previousPeriod, 'name'); + + return currentPeriod.map((currentDependency) => { + const previousDependency = previousPeriodMap[currentDependency.name]; + if (!previousDependency) { + return currentDependency; + } + return { + ...currentDependency, + latency: { + ...currentDependency.latency, + previousPeriodTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentDependency.latency.timeseries, + previousPeriodTimeseries: previousDependency.latency?.timeseries, + }), + }, + throughput: { + ...currentDependency.throughput, + previousPeriodTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentDependency.throughput.timeseries, + previousPeriodTimeseries: previousDependency.throughput?.timeseries, + }), + }, + errorRate: { + ...currentDependency.errorRate, + previousPeriodTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentDependency.errorRate.timeseries, + previousPeriodTimeseries: previousDependency.errorRate?.timeseries, + }), + }, + previousPeriodImpact: previousDependency.impact, + }; + }); +} + export function ServiceOverviewDependenciesTable({ serviceName }: Props) { const { - urlParams: { start, end, environment }, + urlParams: { start, end, environment, comparisonEnabled, comparisonType }, } = useUrlParams(); - const columns: Array> = [ + const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ + start, + end, + comparisonEnabled, + comparisonType, + }); + + const columns: Array> = [ { field: 'name', name: i18n.translate( @@ -102,6 +162,9 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { ); @@ -121,6 +184,11 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { compact color="euiColorVis0" series={throughput.timeseries} + comparisonSeries={ + comparisonEnabled + ? throughput.previousPeriodTimeseries + : undefined + } valueLabel={asTransactionRate(throughput.value)} /> ); @@ -142,6 +210,9 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { compact color="euiColorVis7" series={errorRate.timeseries} + comparisonSeries={ + comparisonEnabled ? errorRate.previousPeriodTimeseries : undefined + } valueLabel={asPercent(errorRate.value, 1)} /> ); @@ -157,13 +228,28 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { } ), width: px(unit * 5), - render: (_, { impact }) => { - return ; + render: (_, { impact, previousPeriodImpact }) => { + return ( + + + + + {comparisonEnabled && previousPeriodImpact !== undefined && ( + + + + )} + + ); }, sortable: true, }, ]; - + // Fetches current period dependencies const { data, status } = useFetcher( (callApmApi) => { if (!start || !end) { @@ -173,22 +259,41 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { return callApmApi({ endpoint: 'GET /api/apm/services/{serviceName}/dependencies', params: { - path: { - serviceName, - }, + path: { serviceName }, + query: { start, end, environment, numBuckets: 20 }, + }, + }); + }, + [start, end, serviceName, environment] + ); + + // Fetches previous period dependencies + const { data: previousPeriodData, status: previousPeriodStatus } = useFetcher( + (callApmApi) => { + if (!comparisonStart || !comparisonEnd) { + return; + } + + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/dependencies', + params: { + path: { serviceName }, query: { - start, - end, + start: comparisonStart, + end: comparisonEnd, environment, numBuckets: 20, }, }, }); }, - [start, end, serviceName, environment] + [comparisonStart, comparisonEnd, serviceName, environment] ); - const serviceDependencies = data?.serviceDependencies ?? []; + const serviceDependencies = mergeCurrentAndPreviousPeriods({ + currentPeriod: data?.serviceDependencies, + previousPeriod: previousPeriodData?.serviceDependencies, + }); // need top-level sortable fields for the managed table const items = serviceDependencies.map((item) => ({ @@ -238,7 +343,10 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { columns={columns} items={items} allowNeutralSort={false} - loading={status === FETCH_STATUS.LOADING} + loading={ + status === FETCH_STATUS.LOADING || + previousPeriodStatus === FETCH_STATUS.LOADING + } pagination={{ initialPageSize: 5, pageSizeOptions: [5], diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 02aad49ddfc9c..9ac1c7d64d8b2 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -162,7 +162,7 @@ export function getColumns({ - {comparisonEnabled && previousImpact && ( + {comparisonEnabled && previousImpact !== undefined && ( diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts index dd41269f0bad6..e45864de2fc1e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts @@ -5,6 +5,7 @@ * 2.0. */ import { keyBy } from 'lodash'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { Coordinate } from '../../../../typings/timeseries'; import { ERROR_GROUP_ID, @@ -17,7 +18,6 @@ import { rangeQuery, kqlQuery, } from '../../../../server/utils/queries'; -import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts index 85414100a1563..804ed91c54a51 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts @@ -6,10 +6,10 @@ */ import { keyBy } from 'lodash'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { Coordinate } from '../../../../typings/timeseries'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; -import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts index 314d6c7bd1458..f14dba69bf404 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts @@ -6,6 +6,7 @@ */ import { keyBy } from 'lodash'; +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -20,7 +21,6 @@ import { kqlQuery, } from '../../../server/utils/queries'; import { Coordinate } from '../../../typings/timeseries'; -import { offsetPreviousPeriodCoordinates } from '../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index ec5dd1308cb7e..71f803a03bf85 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { Coordinate } from '../../../typings/timeseries'; import { @@ -31,7 +32,6 @@ import { getTransactionErrorRateTimeSeries, } from '../helpers/transaction_error_rate'; import { withApmSpan } from '../../utils/with_apm_span'; -import { offsetPreviousPeriodCoordinates } from '../../utils/offset_previous_period_coordinate'; export async function getErrorRate({ environment, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index 468585ddd23cb..5a58360597828 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { @@ -25,7 +26,6 @@ import { } from '../../../lib/helpers/aggregated_transactions'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; -import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getLatencyAggregation, diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 06382c00177d4..54e59f2be7ae3 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -30,7 +30,6 @@ import { getServiceTransactionTypes } from '../lib/services/get_service_transact import { getThroughput } from '../lib/services/get_throughput'; import { getServiceProfilingStatistics } from '../lib/services/profiling/get_service_profiling_statistics'; import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline'; -import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../utils/with_apm_span'; import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; @@ -40,6 +39,7 @@ import { kueryRt, rangeRt, } from './default_api_types'; +import { offsetPreviousPeriodCoordinates } from '../../common/utils/offset_previous_period_coordinate'; const servicesRoute = createApmServerRoute({ endpoint: 'GET /api/apm/services', From ee7ba71b43cfd11e7f671f5d318d881de9767464 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 21 Apr 2021 17:13:48 -0400 Subject: [PATCH 09/33] [Fleet] Always create agent policy as active (#97874) --- x-pack/plugins/fleet/server/services/agent_policy.ts | 1 + .../fleet_api_integration/apis/agent_policy/agent_policy.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 0d1c5c4dd3143..c0a2e80af4bf1 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -212,6 +212,7 @@ class AgentPolicyService { SAVED_OBJECT_TYPE, { ...agentPolicy, + status: 'active', is_managed: agentPolicy.is_managed ?? false, revision: 1, updated_at: new Date().toISOString(), diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index 779c4d767c000..33a4e40482781 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -34,6 +34,7 @@ export default function ({ getService }: FtrProviderContext) { const { body } = await supertest.get(`/api/fleet/agent_policies/${createdPolicy.id}`); expect(body.item.is_managed).to.equal(false); + expect(body.item.status).to.be('active'); }); it('sets given is_managed value', async () => { @@ -140,6 +141,7 @@ export default function ({ getService }: FtrProviderContext) { expect(newPolicy).to.eql({ name: 'Copied policy', + status: 'active', description: 'Test', is_managed: false, namespace: 'default', @@ -242,6 +244,7 @@ export default function ({ getService }: FtrProviderContext) { const { id, updated_at, ...newPolicy } = updatedPolicy; expect(newPolicy).to.eql({ + status: 'active', name: 'Updated name', description: 'Updated description', namespace: 'default', From 0a0fd695d34328cbfaf1a3cd7a835420895ce3dd Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Wed, 21 Apr 2021 17:52:07 -0400 Subject: [PATCH 10/33] [ML][Transform] display script_exception compile error message in wizards (#97697) * ensure error rootCause script gets added to message * ensure script part of error is shown in DFA wizard * use isPopulatedObject to check for script field --- x-pack/plugins/ml/common/util/errors/process_errors.ts | 9 +++++++++ x-pack/plugins/ml/common/util/errors/types.ts | 1 + .../plugins/transform/server/routes/api/error_utils.ts | 7 ++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/common/util/errors/process_errors.ts b/x-pack/plugins/ml/common/util/errors/process_errors.ts index 821ba670e2ddb..e5c6ed38161ab 100644 --- a/x-pack/plugins/ml/common/util/errors/process_errors.ts +++ b/x-pack/plugins/ml/common/util/errors/process_errors.ts @@ -14,6 +14,7 @@ import { isEsErrorBody, isMLResponseError, } from './types'; +import { isPopulatedObject } from '../object_utils'; export const extractErrorProperties = (error: ErrorType): MLErrorObject => { // extract properties of the error object from within the response error @@ -73,6 +74,14 @@ export const extractErrorProperties = (error: ErrorType): MLErrorObject => { error.body.attributes.body.error.caused_by?.caused_by?.reason || error.body.attributes.body.error.caused_by?.reason; } + if ( + Array.isArray(error.body.attributes.body.error.root_cause) && + typeof error.body.attributes.body.error.root_cause[0] === 'object' && + isPopulatedObject(error.body.attributes.body.error.root_cause[0], ['script']) + ) { + errObj.causedBy = error.body.attributes.body.error.root_cause[0].script; + errObj.message += `: '${error.body.attributes.body.error.root_cause[0].script}'`; + } return errObj; } else { return { diff --git a/x-pack/plugins/ml/common/util/errors/types.ts b/x-pack/plugins/ml/common/util/errors/types.ts index 39e9ed4e2575f..3110f09e441cd 100644 --- a/x-pack/plugins/ml/common/util/errors/types.ts +++ b/x-pack/plugins/ml/common/util/errors/types.ts @@ -12,6 +12,7 @@ export interface EsErrorRootCause { type: string; reason: string; caused_by?: EsErrorRootCause; + script?: string; } export interface EsErrorBody { diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index 6a10f170bc6e3..7c4ac88576678 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -152,17 +152,22 @@ interface EsError { reason: string; line?: number; col?: number; + script?: string; } /** * Returns an error message based on the root cause */ -function extractErrorMessage({ type, reason, line, col }: EsError): string { +function extractErrorMessage({ type, reason, script, line, col }: EsError): string { let message = `[${type}] ${reason}`; if (line !== undefined && col !== undefined) { message += `, with line=${line} & col=${col}`; } + if (script !== undefined) { + message += ` '${script}'`; + } + return message; } From c59d0ddeeff179c8ae6632c9f2cc66b5c1c60f38 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 21 Apr 2021 15:37:44 -0700 Subject: [PATCH 11/33] [kbn-es] Increase ES heap (#97905) Signed-off-by: Tyler Smalley --- packages/kbn-es/src/cluster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 2ecfa5c907a23..236d5cf252136 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -272,7 +272,7 @@ exports.Cluster = class Cluster { // especially because we currently run many instances of ES on the same machine during CI options.esEnvVars.ES_JAVA_OPTS = (options.esEnvVars.ES_JAVA_OPTS ? `${options.esEnvVars.ES_JAVA_OPTS} ` : '') + - '-Xms1g -Xmx1g'; + '-Xms2g -Xmx2g'; this._process = execa(ES_BIN, args, { cwd: installPath, From fc15032242d9de570acfcaaa0ab3968383756ad1 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 15:38:52 -0700 Subject: [PATCH 12/33] bump 7.x version in renovate config --- renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index 4dec49ceddf67..86c8f29f2170c 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -54,7 +54,7 @@ packageNames: ['@elastic/elasticsearch'], reviewers: ['team:kibana-operations'], matchBaseBranches: ['7.x'], - labels: ['release_note:skip', 'v7.13.0', 'Team:Operations', 'backport:skip'], + labels: ['release_note:skip', 'v7.14.0', 'Team:Operations', 'backport:skip'], enabled: true, }, { From cbd6bf5b4af8cd3483e58990e6bae46bde44d193 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 21 Apr 2021 17:39:04 -0500 Subject: [PATCH 13/33] [cli/keystore] Transpile sources in dev (#97501) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/cli_keystore/dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli_keystore/dev.js b/src/cli_keystore/dev.js index f7497add69ba0..8cc08fa8e3232 100644 --- a/src/cli_keystore/dev.js +++ b/src/cli_keystore/dev.js @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -require('../setup_node_env/no_transpilation'); +require('../setup_node_env'); require('./cli_keystore'); From 6077816cf7daeffb6bf6dccc2ff0f076e3ad7b73 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 15:39:23 -0700 Subject: [PATCH 14/33] bump version label in elastic/charts section of renovate config --- renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index 86c8f29f2170c..ea41175e1aaab 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -38,7 +38,7 @@ packageNames: ['@elastic/charts'], reviewers: ['markov00', 'nickofthyme'], matchBaseBranches: ['master'], - labels: ['release_note:skip', 'v8.0.0', 'v7.13.0'], + labels: ['release_note:skip', 'v8.0.0', 'v7.14.0'], enabled: true, }, { From 9938f5c9a3fe49b782b1c9e43bb4e637ca2fd151 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 21 Apr 2021 18:05:18 -0500 Subject: [PATCH 15/33] [deb/rpm] Cleanup log and pid folders on package uninstall (#97884) This is a consistency check with elasticsearch, which removes the log and pid folders on package uninstall. Part of #86042 --- .../os_packages/package_scripts/post_remove.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh b/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh index 4e36ae11a29bb..7e587e26ffa9e 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh @@ -34,12 +34,25 @@ case $1 in esac if [ "$REMOVE_DIRS" = "true" ]; then + + if [ -d "<%= logDir %>" ]; then + echo -n "Deleting log directory..." + rm -rf "<%= logDir %>" + echo " OK" + fi + if [ -d "<%= pluginsDir %>" ]; then echo -n "Deleting plugins directory..." rm -rf "<%= pluginsDir %>" echo " OK" fi + if [ -d "<%= pidDir %>" ]; then + echo -n "Deleting PID directory..." + rm -rf "<%= pidDir %>" + echo " OK" + fi + if [ -d "<%= configDir %>" ]; then rmdir --ignore-fail-on-non-empty "<%= configDir %>" fi From 6e64e15f95fb7099ad4db10e1611f3178f55acd6 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Wed, 21 Apr 2021 19:53:08 -0400 Subject: [PATCH 16/33] [App Search] Remove unknowns: 'allow' from enterprise_search routes (#97510) * Remove unknown allows from enterprisesearch routes It is a best practice to avoid `unknowns: allow` in request body validations. Usage of `unknowns: allow` in the App Search plugin has primarily been to avoid creating full validation schemas, as they can often be complex. Rather than have a half-baked validation that uses `unknowns: allow`, we have the option to skip parsing and validating a JSON body entirely, and simply pass it through "as-is" to the enterprise_search server for validation. * Move to helper * Swap comment block style * PR feedback * Update x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts Co-authored-by: Constance * Better TS * Change to TS validations Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Constance --- .../lib/enterprise_search_request_handler.ts | 16 +++- .../server/lib/route_config_helpers.test.ts | 61 ++++++++++++++ .../server/lib/route_config_helpers.ts | 82 +++++++++++++++++++ .../routes/app_search/documents.test.ts | 22 ----- .../server/routes/app_search/documents.ts | 9 +- .../routes/app_search/result_settings.test.ts | 32 -------- .../routes/app_search/result_settings.ts | 19 ++--- .../routes/app_search/search_settings.test.ts | 29 ------- .../routes/app_search/search_settings.ts | 31 ++----- 9 files changed, 172 insertions(+), 129 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index e5394fd580efc..2fc0a13f2ff72 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -79,9 +79,7 @@ export class EnterpriseSearchRequestHandler { // Set up API options const { method } = request.route; const headers = { Authorization: request.headers.authorization as string, ...JSON_HEADER }; - const body = !this.isEmptyObj(request.body as object) - ? JSON.stringify(request.body) - : undefined; + const body = this.getBodyAsString(request.body as object | Buffer); // Call the Enterprise Search API const apiResponse = await fetch(url, { method, headers, body }); @@ -147,6 +145,18 @@ export class EnterpriseSearchRequestHandler { }; } + /** + * There are a number of different expected incoming bodies that we handle & pass on to Enterprise Search for ingestion: + * - Standard object data (should be JSON stringified) + * - Empty (should be passed as undefined and not as an empty obj) + * - Raw buffers (passed on as a string, occurs when using the `skipBodyValidation` lib helper) + */ + getBodyAsString(body: object | Buffer): string | undefined { + if (Buffer.isBuffer(body)) return body.toString(); + if (this.isEmptyObj(body)) return undefined; + return JSON.stringify(body); + } + /** * This path helper is similar to React Router's generatePath, but much simpler & * does not use regexes. It enables us to pass a static '/foo/:bar/baz' string to diff --git a/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts new file mode 100644 index 0000000000000..1843d5f942e37 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +const mockBuffer = {}; +jest.mock('@kbn/config-schema', () => ({ + schema: { + buffer: () => mockBuffer, + object: () => ({}), + }, +})); + +const mockSchema = schema.object({}); + +import { skipBodyValidation } from './route_config_helpers'; + +describe('skipBodyValidation', () => { + it('adds "options.body.parse" and "validate.body" properties to a route config', () => { + expect( + skipBodyValidation({ + path: '/example/path', + validate: {}, + }) + ).toEqual({ + path: '/example/path', + validate: { + body: mockBuffer, + }, + options: { body: { parse: false } }, + }); + }); + + it('persists all other properties, e.g. "path" & other non-"body" properties on "options" & "validate"', () => { + expect( + skipBodyValidation({ + path: '/example/path', + validate: { + params: mockSchema, + }, + options: { + authRequired: true, + }, + }) + ).toEqual({ + path: '/example/path', + validate: { + params: mockSchema, + body: mockBuffer, + }, + options: { + authRequired: true, + body: { parse: false }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts new file mode 100644 index 0000000000000..ab5bbc994a103 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + DestructiveRouteMethod, + RouteConfig, + RouteConfigOptions, + RouteMethod, + RouteValidatorFullConfig, +} from 'kibana/server'; + +import { schema } from '@kbn/config-schema'; + +type Config = RouteConfig; + +// We disallow options to set 'body' because we override them. +interface ConfigWithoutBodyOptions + extends RouteConfig { + validate: Omit, 'body'>; + options?: Omit, 'body'>; +} + +/** + * Kibana Enterprise Search Plugin API endpoints often times pass through the request + * body to the Enterprise Search API endpoints for validation. In those cases, we do not + * need to validate them in Kibana. + * + * The safe way to do that is to turn off body parsing entirely using `options.body.parse: false`. + * The will pass a String Buffer to the route handler. The proper way to validate this when validation + * is enabled to to use `body: schema.buffer()`. + * + * @see https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.routeconfigoptionsbody.md + * @see https://github.com/elastic/kibana/blob/master/packages/kbn-config-schema/README.md#schemabuffer + * + * Example: + * router.put({ + * path: '/api/app_search/engines/{engineName}/example', + * validate: { + * params: schema.object({ + * engineName: schema.string(), + * }), + * body: schema.buffer(), + * }, + * options: { body: { parse: false } }, + * }, + * ... + * + * This helper applies that pattern, while maintaining existing options: + * + * router.put(skipBodyValidation({ + * path: '/api/app_search/engines/{engineName}/example', + * validate: { + * params: schema.object({ + * engineName: schema.string(), + * }), + * }, + * }, + * ... + */ +export const skipBodyValidation = ( + // DestructiveRouteMethod is the Kibana type for everything except 'get' and 'options'. + // Body configuration doesn't apply to those types so we disallow it with this helper. + config: ConfigWithoutBodyOptions +): Config => { + return { + ...config, + validate: { + ...config.validate, + body: schema.buffer(), + }, + options: { + ...(config.options || {}), + body: { + parse: false, + } as RouteConfigOptions['body'], + }, + }; +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts index af54d340ad150..b584412a7a8f3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts @@ -31,28 +31,6 @@ describe('documents routes', () => { path: '/as/engines/:engineName/documents/new', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { body: { documents: [{ foo: 'bar' }] } }; - mockRouter.shouldValidate(request); - }); - - it('missing documents', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - - it('wrong document type', () => { - const request = { body: { documents: ['test'] } }; - mockRouter.shouldThrow(request); - }); - - it('non-array documents type', () => { - const request = { body: { documents: { foo: 'bar' } } }; - mockRouter.shouldThrow(request); - }); - }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts index 78463fc8724ac..929e9f7aef690 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts @@ -7,6 +7,8 @@ import { schema } from '@kbn/config-schema'; +import { skipBodyValidation } from '../../lib/route_config_helpers'; + import { RouteDependencies } from '../../plugin'; export function registerDocumentsRoutes({ @@ -14,17 +16,14 @@ export function registerDocumentsRoutes({ enterpriseSearchRequestHandler, }: RouteDependencies) { router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/documents', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - documents: schema.arrayOf(schema.object({}, { unknowns: 'allow' })), - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/documents/new', }) diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts index e38380d60c6e9..eab4acd1ea4e1 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts @@ -71,22 +71,6 @@ describe('result settings routes', () => { path: '/as/engines/:engineName/result_settings', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { - body: { - result_fields: resultFields, - }, - }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); describe('POST /api/app_search/engines/{name}/sample_response_search', () => { @@ -115,21 +99,5 @@ describe('result settings routes', () => { path: '/as/engines/:engineName/sample_response_search', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { - body: { - query: 'test', - result_fields: resultFields, - }, - }; - mockRouter.shouldValidate(request); - }); - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts index b091ae7a539c2..9a81b8a9f5907 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts @@ -7,9 +7,9 @@ import { schema } from '@kbn/config-schema'; -import { RouteDependencies } from '../../plugin'; +import { skipBodyValidation } from '../../lib/route_config_helpers'; -const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); +import { RouteDependencies } from '../../plugin'; export function registerResultSettingsRoutes({ router, @@ -30,35 +30,28 @@ export function registerResultSettingsRoutes({ ); router.put( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/result_settings', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - result_fields: resultFields, - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/result_settings', }) ); router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/sample_response_search', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - query: schema.string(), - result_fields: schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })), - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/sample_response_search', }) diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts index 26204916deeca..0c11a10ea10f9 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts @@ -107,18 +107,6 @@ describe('search settings routes', () => { path: '/as/engines/:engineName/search_settings', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { body: searchSettings }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); describe('POST /api/app_search/engines/{name}/search_settings/reset', () => { @@ -169,23 +157,6 @@ describe('search settings routes', () => { }); }); - describe('validates body', () => { - it('correctly', () => { - const request = { - body: { - boosts, - search_fields: searchFields, - }, - }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); - describe('validates query', () => { it('correctly', () => { const request = { diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts index 7291f7cfe64f7..c8801d6089730 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts @@ -7,23 +7,9 @@ import { schema } from '@kbn/config-schema'; -import { RouteDependencies } from '../../plugin'; - -// We only do a very light type check here, and allow unknowns, because the request is validated -// on the ent-search server, so it would be redundant to check it here as well. -const boosts = schema.recordOf( - schema.string(), - schema.arrayOf(schema.object({}, { unknowns: 'allow' })) -); -const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); -const searchFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); +import { skipBodyValidation } from '../../lib/route_config_helpers'; -const searchSettingsSchema = schema.object({ - boosts, - result_fields: resultFields, - search_fields: searchFields, - precision: schema.number(), -}); +import { RouteDependencies } from '../../plugin'; export function registerSearchSettingsRoutes({ router, @@ -58,36 +44,31 @@ export function registerSearchSettingsRoutes({ ); router.put( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/search_settings', validate: { params: schema.object({ engineName: schema.string(), }), - body: searchSettingsSchema, }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/search_settings', }) ); router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/search_settings_search', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - boosts: schema.maybe(boosts), - search_fields: searchFields, - }), query: schema.object({ query: schema.string(), }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/search_settings_search', }) From a158ee17aa918d6afc5ac2824781b532435155c5 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 21 Apr 2021 18:53:26 -0500 Subject: [PATCH 17/33] set initial STRING for confirm box (#97907) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/delete_field_modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx index 69092b2bc0922..a9d6d596baf22 100644 --- a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx @@ -87,7 +87,7 @@ export function DeleteFieldModal({ fieldsToDelete, closeModal, confirmDelete }: const i18nTexts = geti18nTexts(fieldsToDelete); const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts; const isMultiple = Boolean(fieldsToDelete.length > 1); - const [confirmContent, setConfirmContent] = useState(); + const [confirmContent, setConfirmContent] = useState(''); return ( Date: Thu, 22 Apr 2021 07:20:28 +0100 Subject: [PATCH 18/33] fix tooltip content (#97847) --- .../public/timelines/components/formatted_ip/index.tsx | 2 +- .../timelines/components/timeline/body/renderers/host_name.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx index 7e3db95e7ddc1..8cdb263fe42bb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx @@ -196,7 +196,7 @@ const AddressLinksItemComponent: React.FC = ({ ) : ( - + = ({ fieldName, contextId, eventId, val Date: Thu, 22 Apr 2021 10:08:33 +0200 Subject: [PATCH 19/33] [Security Solution] User can select event from event list and create a filter (#96940) * Initial version of event filtering form/dialog. Pending to add all redux services * Uses redux store instead of props to get the form values * Manage errors on redux * Creates even filter list on service constructor * Add os type selector depending on form parent by props. Also added create action * Allows add exception to an event. This commit has to be reviewed and maybe it will change depending on next changes * Fix imports because changes on ExceptionBuilder component and add needed type export * Adds constants. Rename eventFilters to eventFilter. Add http wrapper as a hook to check if the list has been created or not * Adds missing files on last commit. * Relocate async resource state to be shared between different pages * Use async resource state to manage async operations on components. Relocate initial entry status to an utils module instead of hook. * Adds comments into redux store from component * Fixes typechecks and wrong imports * Fixes translations and adds subheader and description modal * Relocates form description * Removes unused import * Sanitize entries before submit to remove entry.id * Missed file on last commit * Use specific fields for endpoint_event type builder * Split error field for each kind of errors to prevent unexpected renders. Adds unit test for event filter form component * Set event.kind == event by default * Changes folder names. Add notifications when success. Remove default event.king * Adds notifications when api error and fixed multiple notifications showed for same error * Adds new test for event filter modal and changes component name to be consistent * Adds unit tests for event filter notification * Adds middleware unit tests. Also isolate common event for all tests * Adds unit tests for event filter reducer * Adds unit tests for event filter selector * Fixes same key on different multilanguages. Fixes naming incoherence * Adds feature flag for event filtering * Fixes unit tests and weird behavior when changing items after name or comments on event filter form * Removes unused import * Fixes unit tests. Add imports from lists plugin. Add expects on tests. Change some names * Renames everything from eventFilter to eventFilters (plural) * Rename state variable * Create hook for notifications instead of a component. Removes className from modal body. * Updates available fields for enpoint events builder Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/lists/common/shared_exports.ts | 10 +- .../exceptions/components/builder/index.tsx | 2 +- x-pack/plugins/lists/public/shared_exports.ts | 1 + .../common/shared_imports.ts | 5 + .../events_viewer/events_viewer.test.tsx | 5 + .../exceptionable_endpoint_event_fields.json | 332 ++++++++++++++++++ ...son => exceptionable_endpoint_fields.json} | 0 .../common/components/exceptions/helpers.tsx | 23 +- .../public/common/store/actions.ts | 4 +- .../timeline_actions/alert_context_menu.tsx | 48 ++- .../components/alerts_table/translations.ts | 7 + .../public/management/common/constants.ts | 2 + .../pages/event_filters/constants.ts | 27 ++ .../pages/event_filters/service/index.ts | 47 +++ .../pages/event_filters/state/index.ts | 18 + .../pages/event_filters/store/action.ts | 45 +++ .../pages/event_filters/store/builders.ts | 18 + .../event_filters/store/middleware.test.ts | 129 +++++++ .../pages/event_filters/store/middleware.ts | 73 ++++ .../pages/event_filters/store/reducer.test.ts | 82 +++++ .../pages/event_filters/store/reducer.ts | 83 +++++ .../pages/event_filters/store/selector.ts | 39 ++ .../event_filters/store/selectors.test.ts | 80 +++++ .../pages/event_filters/store/utils.ts | 44 +++ .../pages/event_filters/test_utils/index.ts | 91 +++++ .../view/components/form/index.test.tsx | 118 +++++++ .../view/components/form/index.tsx | 207 +++++++++++ .../view/components/form/translations.ts | 38 ++ .../view/components/modal/index.test.tsx | 170 +++++++++ .../view/components/modal/index.tsx | 133 +++++++ .../view/components/modal/translations.ts | 30 ++ .../pages/event_filters/view/hooks.ts | 44 +++ .../pages/event_filters/view/translations.ts | 27 ++ .../use_event_filters_notification.test.tsx | 117 ++++++ .../pages/trusted_apps/state/index.ts | 2 +- .../state/trusted_apps_list_page_state.ts | 2 +- .../state/async_resource_state.test.ts | 0 .../state/async_resource_state.ts | 4 +- .../public/management/store/middleware.ts | 6 + .../public/management/store/reducer.ts | 5 + .../public/management/types.ts | 2 + .../public/shared_imports.ts | 1 + .../body/events/event_column_view.test.tsx | 5 + .../body/events/event_column_view.tsx | 6 +- .../components/timeline/body/index.test.tsx | 6 + 45 files changed, 2122 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json rename x-pack/plugins/security_solution/public/common/components/exceptions/{exceptionable_fields.json => exceptionable_endpoint_fields.json} (100%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx rename x-pack/plugins/security_solution/public/management/{pages/trusted_apps => }/state/async_resource_state.test.ts (100%) rename x-pack/plugins/security_solution/public/management/{pages/trusted_apps => }/state/async_resource_state.ts (97%) diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index 23da48b35a9d4..286fee6de5425 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -51,4 +51,12 @@ export { export { buildExceptionFilter } from './exceptions'; -export { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from './constants'; +export { + ENDPOINT_LIST_ID, + ENDPOINT_TRUSTED_APPS_LIST_ID, + EXCEPTION_LIST_URL, + EXCEPTION_LIST_ITEM_URL, + ENDPOINT_EVENT_FILTERS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_NAME, + ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, +} from './constants'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx index 5b3e754f7e423..833034aa0a542 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx @@ -7,4 +7,4 @@ export { BuilderEntryItem } from './entry_renderer'; export { BuilderExceptionListItemComponent } from './exception_item_renderer'; -export { ExceptionBuilderComponent } from './exception_items_renderer'; +export { ExceptionBuilderComponent, OnChangeProps } from './exception_items_renderer'; diff --git a/x-pack/plugins/lists/public/shared_exports.ts b/x-pack/plugins/lists/public/shared_exports.ts index 39825e5feb6ba..0aecf8a8f6c85 100644 --- a/x-pack/plugins/lists/public/shared_exports.ts +++ b/x-pack/plugins/lists/public/shared_exports.ts @@ -39,3 +39,4 @@ export { UseExceptionListsSuccess, } from './exceptions/types'; export * as ExceptionBuilder from './exceptions/components/builder/index'; +export { transformNewItemOutput } from './exceptions/transforms'; diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index aaae0d4dc25ef..033df0df6c458 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -45,6 +45,11 @@ export { Type, ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, + EXCEPTION_LIST_URL, + EXCEPTION_LIST_ITEM_URL, + ENDPOINT_EVENT_FILTERS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_NAME, + ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, osType, osTypeArray, OsTypeArray, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 8962f5e6c5146..36986f5f8d353 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -29,6 +29,10 @@ import { SourcererScopeName } from '../../store/sourcerer/model'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; import { useTimelineEvents } from '../../../timelines/containers'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; + +jest.mock('../../hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; jest.mock('../../../timelines/components/graph_overlay', () => ({ GraphOverlay: jest.fn(() =>
), @@ -135,6 +139,7 @@ describe('EventsViewer', () => { }); describe('event details', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); beforeEach(() => { mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponseWithEvents]); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json new file mode 100644 index 0000000000000..2677006d1afac --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json @@ -0,0 +1,332 @@ +[ + "@timestamp", + "agent.id", + "agent.name", + "agent.type", + "agent.version", + "data_stream.dataset", + "data_stream.namespace", + "data_stream.type", + "destination.address", + "destination.bytes", + "destination.domain", + "destination.geo.city_name", + "destination.geo.continent_name", + "destination.geo.country_iso_code", + "destination.geo.country_name", + "destination.geo.location", + "destination.geo.name", + "destination.geo.region_iso_code", + "destination.geo.region_name", + "destination.ip", + "destination.packets", + "destination.port", + "destination.registered_domain", + "destination.top_level_domain", + "dll.code_signature.exists", + "dll.code_signature.status", + "dll.code_signature.subject_name", + "dll.code_signature.trusted", + "dll.code_signature.valid", + "dll.Ext", + "dll.Ext.code_signature", + "dll.Ext.code_signature.exists", + "dll.Ext.code_signature.status", + "dll.Ext.code_signature.subject_name", + "dll.Ext.code_signature.trusted", + "dll.Ext.code_signature.valid", + "dll.Ext.load_index", + "dll.hash.md5", + "dll.hash.sha1", + "dll.hash.sha256", + "dll.hash.sha512", + "dll.name", + "dll.path", + "dll.pe.company", + "dll.pe.description", + "dll.pe.file_version", + "dll.pe.imphash", + "dll.pe.original_file_name", + "dll.pe.product", + "dns.Ext", + "dns.Ext.options", + "dns.Ext.status", + "dns.question.name", + "dns.question.registered_domain", + "dns.question.subdomain", + "dns.question.top_level_domain", + "dns.question.type", + "dns.resolved_ip", + "ecs.version", + "elastic.agent", + "elastic.agent.id", + "Endpoint.policy", + "Endpoint.policy.applied", + "Endpoint.policy.applied.id", + "Endpoint.policy.applied.name", + "Endpoint.policy.applied.status", + "Endpoint.status", + "event.action", + "event.category", + "event.code", + "event.created", + "event.dataset", + "event.Ext", + "event.Ext.correlation", + "event.Ext.correlation.id", + "event.hash", + "event.id", + "event.ingested", + "event.module", + "event.outcome", + "event.provider", + "event.sequence", + "event.severity", + "event.type", + "file.accessed", + "file.attributes", + "file.created", + "file.ctime", + "file.device", + "file.directory", + "file.drive_letter", + "file.Ext", + "file.Ext.code_signature", + "file.Ext.code_signature.exists", + "file.Ext.code_signature.status", + "file.Ext.code_signature.subject_name", + "file.Ext.code_signature.trusted", + "file.Ext.code_signature.valid", + "file.Ext.entropy", + "file.Ext.header_data", + "file.Ext.monotonic_id", + "file.Ext.original", + "file.Ext.original.gid", + "file.Ext.original.group", + "file.Ext.original.mode", + "file.Ext.original.name", + "file.Ext.original.owner", + "file.Ext.original.path", + "file.Ext.original.uid", + "file.Ext.windows", + "file.Ext.windows.zone_identifier", + "file.extension", + "file.gid", + "file.group", + "file.hash.md5", + "file.hash.sha1", + "file.hash.sha256", + "file.hash.sha512", + "file.inode", + "file.mime_type", + "file.mode", + "file.mtime", + "file.name", + "file.owner", + "file.path", + "file.path.caseless", + "file.path.text", + "file.pe.company", + "file.pe.description", + "file.pe.file_version", + "file.pe.imphash", + "file.pe.original_file_name", + "file.pe.product", + "file.size", + "file.target_path", + "file.target_path.caseless", + "file.target_path.text", + "file.type", + "file.uid", + "group.domain", + "group.Ext", + "group.Ext.real", + "group.Ext.real.id", + "group.Ext.real.name", + "group.id", + "group.name", + "host.architecture", + "host.domain", + "host.hostname", + "host.id", + "host.ip", + "host.mac", + "host.name", + "host.os.Ext", + "host.os.Ext.variant", + "host.os.family", + "host.os.full", + "host.os.full.caseless", + "host.os.full.text", + "host.os.kernel", + "host.os.name", + "host.os.name.caseless", + "host.os.name.text", + "host.os.platform", + "host.os.version", + "host.type", + "host.uptime", + "http.request.body.bytes", + "http.request.body.content", + "http.request.body.content.text", + "http.request.bytes", + "http.response.body.bytes", + "http.response.body.content", + "http.response.body.content.text", + "http.response.bytes", + "http.response.Ext", + "http.response.Ext.version", + "http.response.status_code", + "message", + "network.bytes", + "network.community_id", + "network.direction", + "network.iana_number", + "network.packets", + "network.protocol", + "network.transport", + "network.type", + "package.name", + "process.args", + "process.args_count", + "process.code_signature.exists", + "process.code_signature.status", + "process.code_signature.subject_name", + "process.code_signature.trusted", + "process.code_signature.valid", + "process.command_line", + "process.command_line.caseless", + "process.command_line.text", + "process.entity_id", + "process.executable", + "process.executable.caseless", + "process.executable.text", + "process.exit_code", + "process.Ext", + "process.Ext.ancestry", + "process.Ext.authentication_id", + "process.Ext.code_signature", + "process.Ext.code_signature.exists", + "process.Ext.code_signature.status", + "process.Ext.code_signature.subject_name", + "process.Ext.code_signature.trusted", + "process.Ext.code_signature.valid", + "process.Ext.defense_evasions", + "process.Ext.session", + "process.Ext.token.elevation", + "process.Ext.token.elevation_type", + "process.Ext.token.integrity_level_name", + "process.hash.md5", + "process.hash.sha1", + "process.hash.sha256", + "process.hash.sha512", + "process.name", + "process.name.caseless", + "process.name.text", + "process.parent.args", + "process.parent.args_count", + "process.parent.code_signature.exists", + "process.parent.code_signature.status", + "process.parent.code_signature.subject_name", + "process.parent.code_signature.trusted", + "process.parent.code_signature.valid", + "process.parent.command_line", + "process.parent.command_line.caseless", + "process.parent.command_line.text", + "process.parent.entity_id", + "process.parent.executable", + "process.parent.executable.caseless", + "process.parent.executable.text", + "process.parent.exit_code", + "process.parent.Ext", + "process.parent.Ext.code_signature", + "process.parent.Ext.code_signature.exists", + "process.parent.Ext.code_signature.status", + "process.parent.Ext.code_signature.subject_name", + "process.parent.Ext.code_signature.trusted", + "process.parent.Ext.code_signature.valid", + "process.parent.Ext.real", + "process.parent.Ext.real.pid", + "process.parent.hash.md5", + "process.parent.hash.sha1", + "process.parent.hash.sha256", + "process.parent.hash.sha512", + "process.parent.name", + "process.parent.name.caseless", + "process.parent.name.text", + "process.parent.pe.company", + "process.parent.pe.description", + "process.parent.pe.file_version", + "process.parent.pe.imphash", + "process.parent.pe.original_file_name", + "process.parent.pe.product", + "process.parent.pgid", + "process.parent.pid", + "process.parent.ppid", + "process.parent.thread.id", + "process.parent.thread.name", + "process.parent.title", + "process.parent.title.text", + "process.parent.uptime", + "process.parent.working_directory", + "process.parent.working_directory.caseless", + "process.parent.working_directory.text", + "process.pe.company", + "process.pe.description", + "process.pe.file_version", + "process.pe.imphash", + "process.pe.original_file_name", + "process.pe.product", + "process.pgid", + "process.pid", + "process.ppid", + "process.thread.id", + "process.thread.name", + "process.title", + "process.title.text", + "process.uptime", + "process.working_directory", + "process.working_directory.caseless", + "process.working_directory.text", + "registry.data.bytes", + "registry.data.strings", + "registry.hive", + "registry.key", + "registry.path", + "registry.value", + "source.address", + "source.bytes", + "source.domain", + "source.geo.city_name", + "source.geo.continent_name", + "source.geo.country_iso_code", + "source.geo.country_name", + "source.geo.location", + "source.geo.name", + "source.geo.region_iso_code", + "source.geo.region_name", + "source.ip", + "source.packets", + "source.port", + "source.registered_domain", + "source.top_level_domain", + "user.domain", + "user.email", + "user.Ext", + "user.Ext.real", + "user.Ext.real.id", + "user.Ext.real.name", + "user.full_name", + "user.full_name.text", + "user.group.domain", + "user.group.Ext", + "user.group.Ext.real", + "user.group.Ext.real.id", + "user.group.Ext.real.name", + "user.group.id", + "user.group.name", + "user.hash", + "user.id", + "user.name", + "user.name.text" +] \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_fields.json rename to x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 69ec3120a064b..81383bb9c0fa6 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -49,18 +49,29 @@ import { Ecs } from '../../../../common/ecs'; import { CodeSignature } from '../../../../common/ecs/file'; import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard'; import { addIdToItem, removeIdFromItem } from '../../../../common'; -import exceptionableFields from './exceptionable_fields.json'; +import exceptionableEndpointFields from './exceptionable_endpoint_fields.json'; +import exceptionableEndpointEventFields from './exceptionable_endpoint_event_fields.json'; export const filterIndexPatterns = ( patterns: IIndexPattern, type: ExceptionListType ): IIndexPattern => { - return type === 'endpoint' - ? { + switch (type) { + case 'endpoint': + return { ...patterns, - fields: patterns.fields.filter(({ name }) => exceptionableFields.includes(name)), - } - : patterns; + fields: patterns.fields.filter(({ name }) => exceptionableEndpointFields.includes(name)), + }; + case 'endpoint_events': + return { + ...patterns, + fields: patterns.fields.filter(({ name }) => + exceptionableEndpointEventFields.includes(name) + ), + }; + default: + return patterns; + } }; export const addIdToEntries = (entries: EntriesArray): EntriesArray => { diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts index 7e42afc440020..1987edc0e7307 100644 --- a/x-pack/plugins/security_solution/public/common/store/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/actions.ts @@ -8,6 +8,7 @@ import { EndpointAction } from '../../management/pages/endpoint_hosts/store/action'; import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details'; import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store/action'; +import { EventFiltersPageAction } from '../../management/pages/event_filters/store/action'; export { appActions } from './app'; export { dragAndDropActions } from './drag_and_drop'; @@ -19,4 +20,5 @@ export type AppAction = | EndpointAction | RoutingAction | PolicyDetailsAction - | TrustedAppsPageAction; + | TrustedAppsPageAction + | EventFiltersPageAction; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 26b9662a8f19b..a2f568efd3a7d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { getOr } from 'lodash/fp'; +import { indexOf } from 'lodash'; import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; @@ -47,6 +48,7 @@ import { ExceptionListType } from '../../../../../common/shared_imports'; import { AlertData, EcsHit } from '../../../../common/components/exceptions/types'; import { useQueryAlerts } from '../../../containers/detection_engine/alerts/use_query'; import { useSignalIndex } from '../../../containers/detection_engine/alerts/use_signal_index'; +import { EventFiltersModal } from '../../../../management/pages/event_filters/view/components/modal'; interface AlertContextMenuProps { ariaLabel?: string; @@ -81,6 +83,8 @@ const AlertContextMenuComponent: React.FC = ({ '', [ecsRowData] ); + + const isEvent = useMemo(() => indexOf(ecsRowData.event?.kind, 'event') !== -1, [ecsRowData]); const ruleIndices = useMemo((): string[] => { if ( ecsRowData.signal?.rule && @@ -107,6 +111,7 @@ const AlertContextMenuComponent: React.FC = ({ setPopover(false); }, []); const [exceptionModalType, setOpenAddExceptionModal] = useState(null); + const [isAddEventExceptionModalOpen, setIsAddEventExceptionModalOpen] = useState(false); const [{ canUserCRUD, hasIndexWrite, hasIndexMaintenance, hasIndexUpdateDelete }] = useUserData(); const isEndpointAlert = useMemo((): boolean => { @@ -124,6 +129,10 @@ const AlertContextMenuComponent: React.FC = ({ setOpenAddExceptionModal(null); }, []); + const closeAddEventExceptionModal = useCallback((): void => { + setIsAddEventExceptionModalOpen(false); + }, []); + const onAddExceptionCancel = useCallback(() => { closeAddExceptionModal(); }, [closeAddExceptionModal]); @@ -355,6 +364,28 @@ const AlertContextMenuComponent: React.FC = ({ ); }, [handleAddExceptionClick, canUserCRUD, hasIndexWrite]); + const handleAddEventExceptionClick = useCallback((): void => { + closePopover(); + setIsAddEventExceptionModalOpen(true); + }, [closePopover]); + + const addEventExceptionComponent = useMemo( + () => ( + + + {i18n.ACTION_ADD_EVENT_EXCEPTION} + + + ), + [handleAddEventExceptionClick] + ); + const statusFilters = useMemo(() => { if (!alertStatus) { return []; @@ -378,8 +409,18 @@ const AlertContextMenuComponent: React.FC = ({ ]); const items = useMemo( - () => [...statusFilters, addEndpointExceptionComponent, addExceptionComponent], - [addEndpointExceptionComponent, addExceptionComponent, statusFilters] + () => + !isEvent && ruleId + ? [...statusFilters, addEndpointExceptionComponent, addExceptionComponent] + : [addEventExceptionComponent], + [ + addEndpointExceptionComponent, + addExceptionComponent, + addEventExceptionComponent, + statusFilters, + ruleId, + isEvent, + ] ); return ( @@ -412,6 +453,9 @@ const AlertContextMenuComponent: React.FC = ({ onRuleChange={onRuleChange} /> )} + {isAddEventExceptionModalOpen && ecsRowData != null && ( + + )} ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 1829b3822e6a4..56f6337d5a55c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -151,6 +151,13 @@ export const ACTION_ADD_EXCEPTION = i18n.translate( } ); +export const ACTION_ADD_EVENT_EXCEPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.actions.addEventException', + { + defaultMessage: 'Add Endpoint event exception', + } +); + export const ACTION_ADD_ENDPOINT_EXCEPTION = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.actions.addEndpointException', { diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts index 208fe04448709..1b5ddd28d328f 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -26,6 +26,8 @@ export const MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE = 'policyDetails'; export const MANAGEMENT_STORE_ENDPOINTS_NAMESPACE = 'endpoints'; /** Namespace within the Management state where trusted apps page state is maintained */ export const MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE = 'trustedApps'; +/** Namespace within the Management state where event filters page state is maintained */ +export const MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE = 'eventFilters'; export const MANAGEMENT_PAGE_SIZE_OPTIONS: readonly number[] = [10, 20, 50]; export const MANAGEMENT_DEFAULT_PAGE = 0; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts new file mode 100644 index 0000000000000..882b964d54bb5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ExceptionListType, + ExceptionListTypeEnum, + EXCEPTION_LIST_URL, + EXCEPTION_LIST_ITEM_URL, + ENDPOINT_EVENT_FILTERS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_NAME, + ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, +} from '../../../../common/shared_imports'; + +export const EVENT_FILTER_LIST_TYPE: ExceptionListType = ExceptionListTypeEnum.ENDPOINT_EVENTS; +export const EVENT_FILTER_LIST = { + name: ENDPOINT_EVENT_FILTERS_LIST_NAME, + namespace_type: 'agnostic', + description: ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, + list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, + type: EVENT_FILTER_LIST_TYPE, +}; + +export { ENDPOINT_EVENT_FILTERS_LIST_ID, EXCEPTION_LIST_URL, EXCEPTION_LIST_ITEM_URL }; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts new file mode 100644 index 0000000000000..b3521d7499362 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpStart } from 'kibana/public'; +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { EVENT_FILTER_LIST, EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '../constants'; + +export interface EventFiltersService { + addEventFilters( + exception: Immutable + ): Promise; +} +export class EventFiltersHttpService implements EventFiltersService { + private listHasBeenCreated: boolean; + + constructor(private http: HttpStart) { + this.listHasBeenCreated = false; + } + + private async createEndpointEventList() { + try { + await this.http.post(EXCEPTION_LIST_URL, { + body: JSON.stringify(EVENT_FILTER_LIST), + }); + } catch (err) { + // Ignore 409 errors. List already created + if (err.response.status === 409) this.listHasBeenCreated = true; + else throw err; + } + } + + private async httpWrapper() { + if (!this.listHasBeenCreated) await this.createEndpointEventList(); + return this.http; + } + + async addEventFilters(exception: ExceptionListItemSchema | CreateExceptionListItemSchema) { + return (await this.httpWrapper()).post(EXCEPTION_LIST_ITEM_URL, { + body: JSON.stringify(exception), + }); + } +} diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts new file mode 100644 index 0000000000000..b5e867e64888d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { AsyncResourceState } from '../../../state/async_resource_state'; +export interface EventFiltersListPageState { + entries: ExceptionListItemSchema[]; + form: { + entry: CreateExceptionListItemSchema | ExceptionListItemSchema | undefined; + hasNameError: boolean; + hasItemsError: boolean; + submissionResourceState: AsyncResourceState; + }; +} diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts new file mode 100644 index 0000000000000..de2a74ed6f72d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Action } from 'redux'; +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { AsyncResourceState } from '../../../state/async_resource_state'; + +export type EventFiltersInitForm = Action<'eventFiltersInitForm'> & { + payload: { + entry: ExceptionListItemSchema | CreateExceptionListItemSchema; + }; +}; + +export type EventFiltersChangeForm = Action<'eventFiltersChangeForm'> & { + payload: { + entry: ExceptionListItemSchema | CreateExceptionListItemSchema; + hasNameError?: boolean; + hasItemsError?: boolean; + }; +}; + +export type EventFiltersCreateStart = Action<'eventFiltersCreateStart'>; +export type EventFiltersCreateSuccess = Action<'eventFiltersCreateSuccess'> & { + payload: { + exception: ExceptionListItemSchema; + }; +}; +export type EventFiltersCreateError = Action<'eventFiltersCreateError'>; + +export type EventFiltersFormStateChanged = Action<'eventFiltersFormStateChanged'> & { + payload: AsyncResourceState; +}; + +export type EventFiltersPageAction = + | EventFiltersCreateStart + | EventFiltersInitForm + | EventFiltersChangeForm + | EventFiltersCreateStart + | EventFiltersCreateSuccess + | EventFiltersCreateError + | EventFiltersFormStateChanged; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts new file mode 100644 index 0000000000000..86ba9b1e49c38 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EventFiltersListPageState } from '../state'; + +export const initialEventFiltersPageState = (): EventFiltersListPageState => ({ + entries: [], + form: { + entry: undefined, + hasNameError: false, + hasItemsError: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts new file mode 100644 index 0000000000000..afb82ebe011ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { applyMiddleware, createStore, Store } from 'redux'; + +import { + createSpyMiddleware, + MiddlewareActionSpyHelper, +} from '../../../../common/store/test_utils'; +import { AppAction } from '../../../../common/store/actions'; +import { createEventFiltersPageMiddleware } from './middleware'; +import { eventFiltersPageReducer } from './reducer'; +import { EventFiltersService } from '../service'; +import { EventFiltersListPageState } from '../state'; +import { initialEventFiltersPageState } from './builders'; +import { getInitialExceptionFromEvent } from './utils'; +import { createdEventFilterEntryMock, ecsEventMock } from '../test_utils'; + +const initialState: EventFiltersListPageState = initialEventFiltersPageState(); + +const createEventFiltersServiceMock = (): jest.Mocked => ({ + addEventFilters: jest.fn(), +}); + +const createStoreSetup = (eventFiltersService: EventFiltersService) => { + const spyMiddleware = createSpyMiddleware(); + + return { + spyMiddleware, + store: createStore( + eventFiltersPageReducer, + applyMiddleware( + createEventFiltersPageMiddleware(eventFiltersService), + spyMiddleware.actionSpyMiddleware + ) + ), + }; +}; + +describe('middleware', () => { + describe('initial state', () => { + it('sets initial state properly', async () => { + expect(createStoreSetup(createEventFiltersServiceMock()).store.getState()).toStrictEqual( + initialState + ); + }); + }); + + describe('submit creation event filter', () => { + let service: jest.Mocked; + let store: Store; + let spyMiddleware: MiddlewareActionSpyHelper; + + beforeEach(() => { + service = createEventFiltersServiceMock(); + const storeSetup = createStoreSetup(service); + store = storeSetup.store as Store; + spyMiddleware = storeSetup.spyMiddleware; + }); + + it('does not submit when entry is undefined', async () => { + store.dispatch({ type: 'eventFiltersCreateStart' }); + expect(store.getState()).toStrictEqual({ + ...initialState, + form: { + ...store.getState().form, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + + it('does submit when entry is not undefined', async () => { + service.addEventFilters.mockResolvedValue(createdEventFilterEntryMock()); + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + + store.dispatch({ type: 'eventFiltersCreateStart' }); + + await spyMiddleware.waitForAction('eventFiltersFormStateChanged'); + expect(store.getState()).toStrictEqual({ + ...initialState, + form: { + ...store.getState().form, + submissionResourceState: { + type: 'LoadedResourceState', + data: createdEventFilterEntryMock(), + }, + }, + }); + }); + + it('does throw error when creating', async () => { + service.addEventFilters.mockRejectedValue({ + body: { message: 'error message', statusCode: 500, error: 'Internal Server Error' }, + }); + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + + store.dispatch({ type: 'eventFiltersCreateStart' }); + + await spyMiddleware.waitForAction('eventFiltersFormStateChanged'); + expect(store.getState()).toStrictEqual({ + ...initialState, + form: { + ...store.getState().form, + submissionResourceState: { + type: 'FailedResourceState', + lastLoadedState: undefined, + error: { + error: 'Internal Server Error', + message: 'error message', + statusCode: 500, + }, + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts new file mode 100644 index 0000000000000..2f2c7a9e22661 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AppAction } from '../../../../common/store/actions'; +import { + ImmutableMiddleware, + ImmutableMiddlewareAPI, + ImmutableMiddlewareFactory, +} from '../../../../common/store'; + +import { EventFiltersHttpService, EventFiltersService } from '../service'; + +import { EventFiltersListPageState } from '../state'; +import { getLastLoadedResourceState } from '../../../state/async_resource_state'; +import { CreateExceptionListItemSchema, transformNewItemOutput } from '../../../../shared_imports'; + +const eventFiltersCreate = async ( + store: ImmutableMiddlewareAPI, + eventFiltersService: EventFiltersService +) => { + const submissionResourceState = store.getState().form.submissionResourceState; + try { + const formEntry = store.getState().form.entry; + if (!formEntry) return; + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + + const sanitizedEntry = transformNewItemOutput(formEntry as CreateExceptionListItemSchema); + + const exception = await eventFiltersService.addEventFilters(sanitizedEntry); + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: exception, + }, + }); + } catch (error) { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'FailedResourceState', + error: error.body || error, + lastLoadedState: getLastLoadedResourceState(submissionResourceState), + }, + }); + } +}; + +export const createEventFiltersPageMiddleware = ( + eventFiltersService: EventFiltersService +): ImmutableMiddleware => { + return (store) => (next) => async (action) => { + next(action); + + if (action.type === 'eventFiltersCreateStart') { + await eventFiltersCreate(store, eventFiltersService); + } + }; +}; + +export const eventFiltersPageMiddlewareFactory: ImmutableMiddlewareFactory = ( + coreStart +) => createEventFiltersPageMiddleware(new EventFiltersHttpService(coreStart.http)); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts new file mode 100644 index 0000000000000..5ee956ddac3e9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { initialEventFiltersPageState } from './builders'; +import { eventFiltersPageReducer } from './reducer'; +import { getInitialExceptionFromEvent } from './utils'; +import { createdEventFilterEntryMock, ecsEventMock } from '../test_utils'; + +const initialState = initialEventFiltersPageState(); + +describe('reducer', () => { + describe('EventFiltersForm', () => { + it('sets the initial form values', () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + const result = eventFiltersPageReducer(initialState, { + type: 'eventFiltersInitForm', + payload: { entry }, + }); + + expect(result).toStrictEqual({ + ...initialState, + form: { + ...initialState.form, + entry, + hasNameError: !entry.name, + submissionResourceState: { + type: 'UninitialisedResourceState', + }, + }, + }); + }); + + it('change form values', () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + const nameChanged = 'name changed'; + const result = eventFiltersPageReducer(initialState, { + type: 'eventFiltersChangeForm', + payload: { entry: { ...entry, name: nameChanged } }, + }); + + expect(result).toStrictEqual({ + ...initialState, + form: { + ...initialState.form, + entry: { + ...entry, + name: nameChanged, + }, + hasNameError: false, + submissionResourceState: { + type: 'UninitialisedResourceState', + }, + }, + }); + }); + + it('change form status', () => { + const result = eventFiltersPageReducer(initialState, { + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: createdEventFilterEntryMock(), + }, + }); + + expect(result).toStrictEqual({ + ...initialState, + form: { + ...initialState.form, + submissionResourceState: { + type: 'LoadedResourceState', + data: createdEventFilterEntryMock(), + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts new file mode 100644 index 0000000000000..c8f80eaee1852 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ImmutableReducer } from '../../../../common/store'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AppAction } from '../../../../common/store/actions'; + +import { + EventFiltersInitForm, + EventFiltersChangeForm, + EventFiltersFormStateChanged, +} from './action'; + +import { EventFiltersListPageState } from '../state'; +import { initialEventFiltersPageState } from './builders'; + +type StateReducer = ImmutableReducer; +type CaseReducer = ( + state: Immutable, + action: Immutable +) => Immutable; + +const eventFiltersInitForm: CaseReducer = (state, action) => { + return { + ...state, + form: { + ...state.form, + entry: action.payload.entry, + hasNameError: !action.payload.entry.name, + submissionResourceState: { + type: 'UninitialisedResourceState', + }, + }, + }; +}; + +const eventFiltersChangeForm: CaseReducer = (state, action) => { + return { + ...state, + form: { + ...state.form, + entry: action.payload.entry, + hasItemsError: + action.payload.hasItemsError !== undefined + ? action.payload.hasItemsError + : state.form.hasItemsError, + hasNameError: + action.payload.hasNameError !== undefined + ? action.payload.hasNameError + : state.form.hasNameError, + }, + }; +}; + +const eventFiltersFormStateChanged: CaseReducer = (state, action) => { + return { + ...state, + form: { + ...state.form, + submissionResourceState: action.payload, + }, + }; +}; + +export const eventFiltersPageReducer: StateReducer = ( + state = initialEventFiltersPageState(), + action +) => { + switch (action.type) { + case 'eventFiltersInitForm': + return eventFiltersInitForm(state, action); + case 'eventFiltersChangeForm': + return eventFiltersChangeForm(state, action); + case 'eventFiltersFormStateChanged': + return eventFiltersFormStateChanged(state, action); + } + + return state; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts new file mode 100644 index 0000000000000..ece754f71b318 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EventFiltersListPageState } from '../state'; +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { ServerApiError } from '../../../../common/types'; +import { + isLoadingResourceState, + isLoadedResourceState, + isFailedResourceState, +} from '../../../state/async_resource_state'; + +export const getFormEntry = ( + state: EventFiltersListPageState +): CreateExceptionListItemSchema | ExceptionListItemSchema | undefined => { + return state.form.entry; +}; + +export const getFormHasError = (state: EventFiltersListPageState): boolean => { + return state.form.hasItemsError || state.form.hasNameError; +}; + +export const isCreationInProgress = (state: EventFiltersListPageState): boolean => { + return isLoadingResourceState(state.form.submissionResourceState); +}; + +export const isCreationSuccessful = (state: EventFiltersListPageState): boolean => { + return isLoadedResourceState(state.form.submissionResourceState); +}; + +export const getCreationError = (state: EventFiltersListPageState): ServerApiError | undefined => { + const submissionResourceState = state.form.submissionResourceState; + + return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts new file mode 100644 index 0000000000000..94f15907fb58e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { initialEventFiltersPageState } from './builders'; +import { getFormEntry, getFormHasError } from './selector'; +import { ecsEventMock } from '../test_utils'; +import { getInitialExceptionFromEvent } from './utils'; + +const initialState = initialEventFiltersPageState(); + +describe('selectors', () => { + describe('getFormEntry()', () => { + it('returns undefined when there is no entry', () => { + expect(getFormEntry(initialState)).toBe(undefined); + }); + it('returns entry when there is an entry on form', () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + const state = { + ...initialState, + form: { + ...initialState.form, + entry, + }, + }; + expect(getFormEntry(state)).toBe(entry); + }); + }); + describe('getFormHasError()', () => { + it('returns false when there is no entry', () => { + expect(getFormHasError(initialState)).toBeFalsy(); + }); + it('returns true when entry with name error', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasNameError: true, + }, + }; + expect(getFormHasError(state)).toBeTruthy(); + }); + it('returns true when entry with item error', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasItemsError: true, + }, + }; + expect(getFormHasError(state)).toBeTruthy(); + }); + it('returns true when entry with item error and name error', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasItemsError: true, + hasNameError: true, + }, + }; + expect(getFormHasError(state)).toBeTruthy(); + }); + + it('returns false when entry without errors', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasItemsError: false, + hasNameError: false, + }, + }; + expect(getFormHasError(state)).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts new file mode 100644 index 0000000000000..251aaef0897e4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import uuid from 'uuid'; +import { CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { Ecs } from '../../../../../common/ecs'; +import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '../constants'; + +export const getInitialExceptionFromEvent = (data: Ecs): CreateExceptionListItemSchema => ({ + comments: [], + description: '', + entries: + data.event && data.process + ? [ + { + field: 'event.category', + operator: 'included', + type: 'match', + value: (data.event.category ?? [])[0], + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: (data.process.executable ?? [])[0], + }, + ] + : [], + item_id: undefined, + list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, + meta: { + temporaryUuid: uuid.v4(), + }, + name: '', + namespace_type: 'agnostic', + tags: [], + type: 'simple', + // TODO: Try to fix this type casting + os_types: [(data.host ? data.host.os?.family ?? [] : [])[0] as 'windows' | 'linux' | 'macos'], +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts new file mode 100644 index 0000000000000..c38de842521f5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { combineReducers, createStore } from 'redux'; +import { Ecs } from '../../../../../common/ecs'; + +import { + MANAGEMENT_STORE_GLOBAL_NAMESPACE, + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, +} from '../../../common/constants'; +import { ExceptionListItemSchema } from '../../../../shared_imports'; + +import { eventFiltersPageReducer } from '../store/reducer'; + +export const createGlobalNoMiddlewareStore = () => { + return createStore( + combineReducers({ + [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({ + [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: eventFiltersPageReducer, + }), + }) + ); +}; + +export const ecsEventMock = (): Ecs => ({ + _id: 'unLfz3gB2mJZsMY3ytx3', + timestamp: '2021-04-14T15:34:15.330Z', + _index: '.ds-logs-endpoint.events.process-default-2021.04.12-000001', + event: { + category: ['network'], + id: ['2c4f51be-7736-4ab8-a255-54e7023c4653'], + kind: ['event'], + type: ['start'], + }, + host: { + name: ['Host-tvs68wo3qc'], + os: { + family: ['windows'], + }, + id: ['a563b365-2bee-40df-adcd-ae84d889f523'], + ip: ['10.242.233.187'], + }, + user: { + name: ['uegem17ws4'], + domain: ['hr8jofpkxp'], + }, + agent: { + type: ['endpoint'], + }, + process: { + hash: { + md5: ['c4653870-99b8-4f36-abde-24812d08a289'], + }, + parent: { + pid: [4852], + }, + pid: [3652], + name: ['lsass.exe'], + args: ['"C:\\lsass.exe" \\6z9'], + entity_id: ['9qotd1i8rf'], + executable: ['C:\\lsass.exe'], + }, +}); + +export const createdEventFilterEntryMock = (): ExceptionListItemSchema => ({ + _version: 'WzM4MDgsMV0=', + meta: undefined, + comments: [], + created_at: '2021-04-19T10:30:36.425Z', + created_by: 'elastic', + description: '', + entries: [ + { field: 'event.category', operator: 'included', type: 'match', value: 'process' }, + { field: 'process.executable', operator: 'included', type: 'match', value: 'C:\\iexlorer.exe' }, + ], + id: '47598790-a0fa-11eb-8458-69ac85f1fa18', + item_id: '93f65a04-6f5c-4f9e-9be5-e674b3c2392f', + list_id: '.endpointEventFilterList', + name: 'Test', + namespace_type: 'agnostic', + os_types: ['windows'], + tags: [], + tie_breaker_id: 'c42f3dbd-292f-49e8-83ab-158d024a4d8b', + type: 'simple', + updated_at: '2021-04-19T10:30:36.428Z', + updated_by: 'elastic', +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx new file mode 100644 index 0000000000000..d0d8ea12cf160 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EventFiltersForm } from '.'; +import { RenderResult, act, render } from '@testing-library/react'; +import { fireEvent } from '@testing-library/dom'; +import { stubIndexPatternWithFields } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; +import { getInitialExceptionFromEvent } from '../../../store/utils'; +import { Provider } from 'react-redux'; +import { useFetchIndex } from '../../../../../../common/containers/source'; +import { ThemeProvider } from 'styled-components'; +import { createGlobalNoMiddlewareStore, ecsEventMock } from '../../../test_utils'; +import { getMockTheme } from '../../../../../../common/lib/kibana/kibana_react.mock'; +import { NAME_ERROR, NAME_PLACEHOLDER } from './translations'; +import { useCurrentUser, useKibana } from '../../../../../../common/lib/kibana'; + +jest.mock('../../../../../../common/lib/kibana'); +jest.mock('../../../../../../common/containers/source'); + +const mockTheme = getMockTheme({ + eui: { + paddingSizes: { m: '2' }, + }, +}); + +describe('Event filter form', () => { + let component: RenderResult; + let store: ReturnType; + + const renderForm = () => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + return render(, { wrapper: Wrapper }); + }; + + const renderComponentWithdata = () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + act(() => { + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + }); + return renderForm(); + }; + + beforeEach(() => { + (useFetchIndex as jest.Mock).mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPatternWithFields, + }, + ]); + (useCurrentUser as jest.Mock).mockReturnValue({ username: 'test-username' }); + (useKibana as jest.Mock).mockReturnValue({ + services: { + http: {}, + data: {}, + notifications: {}, + }, + }); + store = createGlobalNoMiddlewareStore(); + }); + it('should renders correctly without data', () => { + component = renderForm(); + expect(component.getByTestId('loading-spinner')).not.toBeNull(); + }); + + it('should renders correctly with data', () => { + component = renderComponentWithdata(); + + expect(component.getByText(ecsEventMock().process!.executable![0])).not.toBeNull(); + expect(component.getByText(NAME_ERROR)).not.toBeNull(); + }); + + it('should change name', async () => { + component = renderComponentWithdata(); + + const nameInput = component.getByPlaceholderText(NAME_PLACEHOLDER); + + act(() => { + fireEvent.change(nameInput, { + target: { + value: 'Exception name', + }, + }); + }); + + expect(store.getState()!.management!.eventFilters!.form!.entry!.name).toBe('Exception name'); + expect(store.getState()!.management!.eventFilters!.form!.hasNameError).toBeFalsy(); + }); + + it('should change comments', async () => { + component = renderComponentWithdata(); + + const commentInput = component.getByPlaceholderText('Add a new comment...'); + + act(() => { + fireEvent.change(commentInput, { + target: { + value: 'Exception comment', + }, + }); + }); + + expect(store.getState()!.management!.eventFilters!.form!.entry!.comments![0].comment).toBe( + 'Exception comment' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx new file mode 100644 index 0000000000000..2aeb53ed5bcb2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useMemo, useCallback, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + EuiFieldText, + EuiSpacer, + EuiForm, + EuiFormRow, + EuiSuperSelect, + EuiSuperSelectOption, + EuiText, +} from '@elastic/eui'; + +import { isEmpty } from 'lodash'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { AddExceptionComments } from '../../../../../../common/components/exceptions/add_exception_comments'; +import { filterIndexPatterns } from '../../../../../../common/components/exceptions/helpers'; +import { Loader } from '../../../../../../common/components/loader'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import { useFetchIndex } from '../../../../../../common/containers/source'; +import { AppAction } from '../../../../../../common/store/actions'; +import { ExceptionListItemSchema, ExceptionBuilder } from '../../../../../../shared_imports'; + +import { useEventFiltersSelector } from '../../hooks'; +import { getFormEntry } from '../../../store/selector'; +import { + FORM_DESCRIPTION, + NAME_LABEL, + NAME_ERROR, + NAME_PLACEHOLDER, + OS_LABEL, + RULE_NAME, +} from './translations'; +import { OS_TITLES } from '../../../../../common/translations'; +import { ENDPOINT_EVENT_FILTERS_LIST_ID, EVENT_FILTER_LIST_TYPE } from '../../../constants'; + +const OPERATING_SYSTEMS: readonly OperatingSystem[] = [ + OperatingSystem.MAC, + OperatingSystem.WINDOWS, + OperatingSystem.LINUX, +]; + +interface EventFiltersFormProps { + allowSelectOs?: boolean; +} +export const EventFiltersForm: React.FC = memo( + ({ allowSelectOs = false }) => { + const { http, data } = useKibana().services; + const dispatch = useDispatch>(); + const exception = useEventFiltersSelector(getFormEntry); + + const [isIndexPatternLoading, { indexPatterns }] = useFetchIndex(['logs-endpoint.events.*']); + + const osOptions: Array> = useMemo( + () => OPERATING_SYSTEMS.map((os) => ({ value: os, inputDisplay: OS_TITLES[os] })), + [] + ); + + const [hasNameError, setHasNameError] = useState(!exception || !exception.name); + const [comment, setComment] = useState(''); + + const handleOnBuilderChange = useCallback( + (arg: ExceptionBuilder.OnChangeProps) => { + if (isEmpty(arg.exceptionItems)) return; + dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { + ...arg.exceptionItems[0], + name: exception?.name ?? '', + comments: exception?.comments ?? [], + }, + hasItemsError: arg.errorExists, + }, + }); + }, + [dispatch, exception?.name, exception?.comments] + ); + + const handleOnChangeName = useCallback( + (e: React.ChangeEvent) => { + if (!exception) return; + setHasNameError(!e.target.value); + dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { ...exception, name: e.target.value.toString() }, + hasNameError: !e.target.value, + }, + }); + }, + [dispatch, exception] + ); + + const handleOnChangeComment = useCallback( + (value: string) => { + setComment(value); + if (!exception) return; + dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { ...exception, comments: [{ comment: value }] }, + }, + }); + }, + [dispatch, exception, setComment] + ); + + const exceptionBuilderComponentMemo = useMemo( + () => ( + + ), + [data, handleOnBuilderChange, http, indexPatterns, exception] + ); + + const nameInputMemo = useMemo( + () => ( + + + + ), + [hasNameError, exception?.name, handleOnChangeName] + ); + + const osInputMemo = useMemo( + () => ( + + + + ), + [exception?.os_types, osOptions] + ); + + const commentsInputMemo = useMemo( + () => ( + + ), + [comment, handleOnChangeComment] + ); + + return !isIndexPatternLoading && exception ? ( + + {FORM_DESCRIPTION} + + {nameInputMemo} + + {allowSelectOs ? ( + <> + {osInputMemo} + + + ) : null} + {exceptionBuilderComponentMemo} + + {commentsInputMemo} + + ) : ( + + ); + } +); + +EventFiltersForm.displayName = 'EventFiltersForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts new file mode 100644 index 0000000000000..79cf296928e84 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const FORM_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.eventFilter.modal.description', + { + defaultMessage: "Events are filtered when the rule's conditions are met:", + } +); + +export const NAME_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.eventFilter.form.name.placeholder', + { + defaultMessage: 'Event exception name', + } +); + +export const NAME_LABEL = i18n.translate('xpack.securitySolution.eventFilter.form.name.label', { + defaultMessage: 'Name your event exception', +}); + +export const NAME_ERROR = i18n.translate('xpack.securitySolution.eventFilter.form.name.error', { + defaultMessage: "The name can't be empty", +}); + +export const OS_LABEL = i18n.translate('xpack.securitySolution.eventFilter.form.os.label', { + defaultMessage: 'Seelct OS', +}); + +export const RULE_NAME = i18n.translate('xpack.securitySolution.eventFilter.form.rule.name', { + defaultMessage: 'Endpoint Event Filtering', +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx new file mode 100644 index 0000000000000..0c976b3571515 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EventFiltersModal } from '.'; +import { RenderResult, act, render } from '@testing-library/react'; +import { fireEvent } from '@testing-library/dom'; +import { Provider } from 'react-redux'; +import { ThemeProvider } from 'styled-components'; +import { createGlobalNoMiddlewareStore, ecsEventMock } from '../../../test_utils'; +import { getMockTheme } from '../../../../../../common/lib/kibana/kibana_react.mock'; +import { MODAL_TITLE, MODAL_SUBTITLE, ACTIONS_CONFIRM, ACTIONS_CANCEL } from './translations'; +import { + CreateExceptionListItemSchema, + ExceptionListItemSchema, +} from '../../../../../../shared_imports'; + +jest.mock('../form'); +jest.mock('../../hooks', () => { + const originalModule = jest.requireActual('../../hooks'); + const useEventFiltersNotification = jest.fn().mockImplementation(() => {}); + + return { + ...originalModule, + useEventFiltersNotification, + }; +}); + +const mockTheme = getMockTheme({ + eui: { + paddingSizes: { m: '2' }, + euiBreakpoints: { l: '2' }, + }, +}); + +describe('Event filter modal', () => { + let component: RenderResult; + let store: ReturnType; + let onCancelMock: jest.Mock; + + const renderForm = () => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + return render(, { + wrapper: Wrapper, + }); + }; + + beforeEach(() => { + store = createGlobalNoMiddlewareStore(); + onCancelMock = jest.fn(); + }); + + it('should renders correctly', () => { + component = renderForm(); + expect(component.getAllByText(MODAL_TITLE)).not.toBeNull(); + expect(component.getByText(MODAL_SUBTITLE)).not.toBeNull(); + expect(component.getAllByText(ACTIONS_CONFIRM)).not.toBeNull(); + expect(component.getByText(ACTIONS_CANCEL)).not.toBeNull(); + }); + + it('should dispatch action to init form store on mount', () => { + component = renderForm(); + expect(store.getState()!.management!.eventFilters!.form!.entry).not.toBeNull(); + }); + + it('should confirm form when button is disabled', () => { + component = renderForm(); + const confirmButton = component.getByTestId('add-exception-confirm-button'); + act(() => { + fireEvent.click(confirmButton); + }); + expect(store.getState()!.management!.eventFilters!.form!.submissionResourceState.type).toBe( + 'UninitialisedResourceState' + ); + }); + + it('should confirm form when button is enabled', () => { + component = renderForm(); + store.dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { + ...(store.getState()!.management!.eventFilters!.form! + .entry as CreateExceptionListItemSchema), + name: 'test', + }, + hasNameError: false, + }, + }); + const confirmButton = component.getByTestId('add-exception-confirm-button'); + act(() => { + fireEvent.click(confirmButton); + }); + expect(store.getState()!.management!.eventFilters!.form!.submissionResourceState.type).toBe( + 'UninitialisedResourceState' + ); + expect(confirmButton.hasAttribute('disabled')).toBeFalsy(); + }); + + it('should close when exception has been submitted correctly', () => { + component = renderForm(); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: store.getState()!.management!.eventFilters!.form!.entry as ExceptionListItemSchema, + }, + }); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + }); + + it('should close when click on cancel button', () => { + component = renderForm(); + const cancelButton = component.getByText(ACTIONS_CANCEL); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + fireEvent.click(cancelButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + }); + + it('should close when close modal', () => { + component = renderForm(); + const modalCloseButton = component.getByLabelText('Closes this modal window'); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + fireEvent.click(modalCloseButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + }); + + it('should prevent close when is loading action', () => { + component = renderForm(); + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + + const cancelButton = component.getByText(ACTIONS_CANCEL); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + fireEvent.click(cancelButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx new file mode 100644 index 0000000000000..50102d09248b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useMemo, useEffect, useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import styled, { css } from 'styled-components'; +import { + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalFooter, + EuiButton, + EuiButtonEmpty, +} from '@elastic/eui'; +import { AppAction } from '../../../../../../common/store/actions'; +import { Ecs } from '../../../../../../../common/ecs'; +import { EventFiltersForm } from '../form'; +import { useEventFiltersSelector, useEventFiltersNotification } from '../../hooks'; +import { + getFormHasError, + isCreationInProgress, + isCreationSuccessful, +} from '../../../store/selector'; +import { getInitialExceptionFromEvent } from '../../../store/utils'; +import { MODAL_TITLE, MODAL_SUBTITLE, ACTIONS_CONFIRM, ACTIONS_CANCEL } from './translations'; + +export interface EventFiltersModalProps { + data: Ecs; + onCancel(): void; +} + +const Modal = styled(EuiModal)` + ${({ theme }) => css` + width: ${theme.eui.euiBreakpoints.l}; + max-width: ${theme.eui.euiBreakpoints.l}; + `} +`; + +const ModalHeader = styled(EuiModalHeader)` + flex-direction: column; + align-items: flex-start; +`; + +const ModalHeaderSubtitle = styled.div` + ${({ theme }) => css` + color: ${theme.eui.euiColorMediumShade}; + `} +`; + +const ModalBodySection = styled.section` + ${({ theme }) => css` + padding: ${theme.eui.euiSizeS} ${theme.eui.euiSizeL}; + overflow-y: scroll; + `} +`; + +export const EventFiltersModal: React.FC = memo(({ data, onCancel }) => { + useEventFiltersNotification(); + const dispatch = useDispatch>(); + const formHasError = useEventFiltersSelector(getFormHasError); + const creationInProgress = useEventFiltersSelector(isCreationInProgress); + const creationSuccessful = useEventFiltersSelector(isCreationSuccessful); + + useEffect(() => { + if (creationSuccessful) { + onCancel(); + dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); + } + }, [creationSuccessful, onCancel, dispatch]); + + // Initialize the store with the event passed as prop to allow render the form. It acts as componentDidMount + useEffect(() => { + dispatch({ + type: 'eventFiltersInitForm', + payload: { entry: getInitialExceptionFromEvent(data) }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleOnCancel = useCallback(() => { + if (creationInProgress) return; + onCancel(); + }, [creationInProgress, onCancel]); + + const confirmButtonMemo = useMemo( + () => ( + { + dispatch({ type: 'eventFiltersCreateStart' }); + }} + isLoading={creationInProgress} + > + {ACTIONS_CONFIRM} + + ), + [dispatch, formHasError, creationInProgress] + ); + + return ( + + + {MODAL_TITLE} + {MODAL_SUBTITLE} + + + + + + + + + {ACTIONS_CANCEL} + + {confirmButtonMemo} + + + ); +}); + +EventFiltersModal.displayName = 'EventFiltersModal'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts new file mode 100644 index 0000000000000..982d9b3bb12b3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const MODAL_TITLE = i18n.translate('xpack.securitySolution.eventFilter.modal.title', { + defaultMessage: 'Add Endpoint Event Filter', +}); + +export const MODAL_SUBTITLE = i18n.translate('xpack.securitySolution.eventFilter.modal.subtitle', { + defaultMessage: 'Endpoint Security', +}); + +export const ACTIONS_CONFIRM = i18n.translate( + 'xpack.securitySolution.eventFilter.modal.actions.confirm', + { + defaultMessage: 'Add Endpoint Event Filter', + } +); + +export const ACTIONS_CANCEL = i18n.translate( + 'xpack.securitySolution.eventFilter.modal.actions.cancel', + { + defaultMessage: 'cancel', + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts new file mode 100644 index 0000000000000..407dee896f5ac --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { isCreationSuccessful, getFormEntry, getCreationError } from '../store/selector'; + +import { useToasts } from '../../../../common/lib/kibana'; +import { getCreationSuccessMessage, getCreationErrorMessage } from './translations'; + +import { State } from '../../../../common/store'; +import { EventFiltersListPageState } from '../state'; + +import { + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE as EVENT_FILTER_NS, + MANAGEMENT_STORE_GLOBAL_NAMESPACE as GLOBAL_NS, +} from '../../../common/constants'; + +export function useEventFiltersSelector(selector: (state: EventFiltersListPageState) => R): R { + return useSelector((state: State) => + selector(state[GLOBAL_NS][EVENT_FILTER_NS] as EventFiltersListPageState) + ); +} + +export const useEventFiltersNotification = () => { + const creationSuccessful = useEventFiltersSelector(isCreationSuccessful); + const creationError = useEventFiltersSelector(getCreationError); + const formEntry = useEventFiltersSelector(getFormEntry); + const toasts = useToasts(); + const [wasAlreadyHandled] = useState(new WeakSet()); + + if (creationSuccessful && formEntry && !wasAlreadyHandled.has(formEntry)) { + wasAlreadyHandled.add(formEntry); + toasts.addSuccess(getCreationSuccessMessage(formEntry)); + } else if (creationError && !wasAlreadyHandled.has(creationError)) { + wasAlreadyHandled.add(creationError); + toasts.addDanger(getCreationErrorMessage(creationError)); + } +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts new file mode 100644 index 0000000000000..711ab8224ea60 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { ServerApiError } from '../../../../common/types'; + +export const getCreationSuccessMessage = ( + entry: CreateExceptionListItemSchema | ExceptionListItemSchema | undefined +) => { + return i18n.translate('xpack.securitySolution.eventFilter.form.successToastTitle', { + defaultMessage: '"{name}" has been added to the event exceptions list.', + values: { name: entry?.name }, + }); +}; + +export const getCreationErrorMessage = (creationError: ServerApiError) => { + return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle', { + defaultMessage: 'There was an error creating the new exception: "{error}"', + values: { error: creationError.message }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx new file mode 100644 index 0000000000000..37f7dff8408a7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Provider } from 'react-redux'; +import { renderHook, act } from '@testing-library/react-hooks'; + +import { NotificationsStart } from 'kibana/public'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public/context'; +import { CreateExceptionListItemSchema, ExceptionListItemSchema } from '../../../../shared_imports'; + +import { createGlobalNoMiddlewareStore, ecsEventMock } from '../test_utils'; +import { useEventFiltersNotification } from './hooks'; +import { getCreationErrorMessage, getCreationSuccessMessage } from './translations'; +import { getInitialExceptionFromEvent } from '../store/utils'; +import { + getLastLoadedResourceState, + FailedResourceState, +} from '../../../state/async_resource_state'; + +const mockNotifications = () => coreMock.createStart({ basePath: '/mock' }).notifications; + +const renderNotifications = ( + store: ReturnType, + notifications: NotificationsStart +) => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + return renderHook(useEventFiltersNotification, { wrapper: Wrapper }); +}; + +describe('EventFiltersNotification', () => { + it('renders correctly initially', () => { + const notifications = mockNotifications(); + + renderNotifications(createGlobalNoMiddlewareStore(), notifications); + + expect(notifications.toasts.addSuccess).not.toBeCalled(); + expect(notifications.toasts.addDanger).not.toBeCalled(); + }); + + it('shows success notification when creation successful', () => { + const store = createGlobalNoMiddlewareStore(); + const notifications = mockNotifications(); + + renderNotifications(store, notifications); + + act(() => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + }); + + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: store.getState()!.management!.eventFilters!.form!.entry as ExceptionListItemSchema, + }, + }); + }); + + expect(notifications.toasts.addSuccess).toBeCalledWith( + getCreationSuccessMessage( + store.getState()!.management!.eventFilters!.form!.entry as CreateExceptionListItemSchema + ) + ); + expect(notifications.toasts.addDanger).not.toBeCalled(); + }); + + it('shows error notification when creation fails', () => { + const store = createGlobalNoMiddlewareStore(); + const notifications = mockNotifications(); + + renderNotifications(store, notifications); + + act(() => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + }); + + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'FailedResourceState', + error: { message: 'error message', statusCode: 500, error: 'error' }, + lastLoadedState: getLastLoadedResourceState( + store.getState()!.management!.eventFilters!.form!.submissionResourceState + ), + }, + }); + }); + + expect(notifications.toasts.addSuccess).not.toBeCalled(); + expect(notifications.toasts.addDanger).toBeCalledWith( + getCreationErrorMessage( + (store.getState()!.management!.eventFilters!.form! + .submissionResourceState as FailedResourceState).error + ) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts index 308dbf3df5f73..a06fceab29d4c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export * from './async_resource_state'; +export * from '../../../state/async_resource_state'; export * from './trusted_apps_list_page_state'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts index e37b0f262603d..5041f0a6118dc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts @@ -6,7 +6,7 @@ */ import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps'; -import { AsyncResourceState } from '.'; +import { AsyncResourceState } from '../../../state/async_resource_state'; import { GetPolicyListResponse } from '../../policy/types'; export interface Pagination { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts b/x-pack/plugins/security_solution/public/management/state/async_resource_state.test.ts similarity index 100% rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts rename to x-pack/plugins/security_solution/public/management/state/async_resource_state.test.ts diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts b/x-pack/plugins/security_solution/public/management/state/async_resource_state.ts similarity index 97% rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts rename to x-pack/plugins/security_solution/public/management/state/async_resource_state.ts index 5208d534221a8..1b6dec54ec0b3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts +++ b/x-pack/plugins/security_solution/public/management/state/async_resource_state.ts @@ -15,8 +15,8 @@ * - update can fail due to multiple reasons and also needs to be communicated to the user */ -import { Immutable } from '../../../../../common/endpoint/types'; -import { ServerApiError } from '../../../../common/types'; +import { Immutable } from '../../../common/endpoint/types'; +import { ServerApiError } from '../../common/types'; /** * Data type to represent uninitialised state of asynchronous resource. diff --git a/x-pack/plugins/security_solution/public/management/store/middleware.ts b/x-pack/plugins/security_solution/public/management/store/middleware.ts index aff1f7db7f066..dae2357d5bb10 100644 --- a/x-pack/plugins/security_solution/public/management/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/store/middleware.ts @@ -15,10 +15,12 @@ import { MANAGEMENT_STORE_GLOBAL_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, } from '../common/constants'; import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details'; import { endpointMiddlewareFactory } from '../pages/endpoint_hosts/store/middleware'; import { trustedAppsPageMiddlewareFactory } from '../pages/trusted_apps/store/middleware'; +import { eventFiltersPageMiddlewareFactory } from '../pages/event_filters/store/middleware'; type ManagementSubStateKey = keyof State[typeof MANAGEMENT_STORE_GLOBAL_NAMESPACE]; @@ -42,5 +44,9 @@ export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( createSubStateSelector(MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE), trustedAppsPageMiddlewareFactory(coreStart, depsStart) ), + substateMiddlewareFactory( + createSubStateSelector(MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE), + eventFiltersPageMiddlewareFactory(coreStart, depsStart) + ), ]; }; diff --git a/x-pack/plugins/security_solution/public/management/store/reducer.ts b/x-pack/plugins/security_solution/public/management/store/reducer.ts index 683840d852271..bf8cd416a3e39 100644 --- a/x-pack/plugins/security_solution/public/management/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/store/reducer.ts @@ -14,6 +14,7 @@ import { MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, } from '../common/constants'; import { ImmutableCombineReducers } from '../../common/store'; import { Immutable } from '../../../common/endpoint/types'; @@ -24,6 +25,8 @@ import { } from '../pages/endpoint_hosts/store/reducer'; import { initialTrustedAppsPageState } from '../pages/trusted_apps/store/builders'; import { trustedAppsPageReducer } from '../pages/trusted_apps/store/reducer'; +import { initialEventFiltersPageState } from '../pages/event_filters/store/builders'; +import { eventFiltersPageReducer } from '../pages/event_filters/store/reducer'; const immutableCombineReducers: ImmutableCombineReducers = combineReducers; @@ -34,6 +37,7 @@ export const mockManagementState: Immutable = { [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialEndpointListState, [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState(), + [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: initialEventFiltersPageState(), }; /** @@ -43,4 +47,5 @@ export const managementReducer = immutableCombineReducers({ [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer, [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: endpointListReducer, [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, + [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: eventFiltersPageReducer, }); diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index a21f182fb9d67..902010a97603c 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -10,6 +10,7 @@ import { SecurityPageName } from '../app/types'; import { PolicyDetailsState } from './pages/policy/types'; import { EndpointState } from './pages/endpoint_hosts/types'; import { TrustedAppsListPageState } from './pages/trusted_apps/state'; +import { EventFiltersListPageState } from './pages/event_filters/state'; /** * The type for the management store global namespace. Used mostly internally to reference @@ -21,6 +22,7 @@ export type ManagementState = CombinedState<{ policyDetails: PolicyDetailsState; endpoints: EndpointState; trustedApps: TrustedAppsListPageState; + eventFilters: EventFiltersListPageState; }>; /** diff --git a/x-pack/plugins/security_solution/public/shared_imports.ts b/x-pack/plugins/security_solution/public/shared_imports.ts index 757191fdb54ec..4a1fdbf0564d1 100644 --- a/x-pack/plugins/security_solution/public/shared_imports.ts +++ b/x-pack/plugins/security_solution/public/shared_imports.ts @@ -59,4 +59,5 @@ export { addEndpointExceptionList, withOptionalSignal, ExceptionBuilder, + transformNewItemOutput, } from '../../lists/public'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 74724dedf4d11..deb94521cde0b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -17,6 +17,10 @@ import { EventColumnView } from './event_column_view'; import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer'; import { TimelineTabs, TimelineType, TimelineId } from '../../../../../../common/types/timeline'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; + +jest.mock('../../../../../common/hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; jest.mock('../../../../../common/hooks/use_selector'); @@ -29,6 +33,7 @@ jest.mock('../../../../../cases/components/timeline_actions/add_to_case_action', }); describe('EventColumnView', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); (useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default); const props = { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index a0a0aeb23e8f7..cb1fff1cf9562 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react'; import { CellValueElementProps } from '../../cell_rendering'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { Ecs } from '../../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; @@ -96,6 +97,8 @@ export const EventColumnView = React.memo( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).timelineType ); + const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled'); + // Each action button shall announce itself to screen readers via an `aria-label` // in the following format: // "button description, for the event in row {ariaRowindex}, with columns {columnValues}", @@ -183,7 +186,7 @@ export const EventColumnView = React.memo( key="alert-context-menu" ecsRowData={ecsData} timelineId={timelineId} - disabled={eventType !== 'signal'} + disabled={eventType !== 'signal' && (!isEventFilteringEnabled || eventType !== 'raw')} refetch={refetch} onRuleChange={onRuleChange} />, @@ -205,6 +208,7 @@ export const EventColumnView = React.memo( timelineId, timelineType, toggleShowNotes, + isEventFilteringEnabled, ] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 76dbfc553d228..b526ce7e0d996 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -21,6 +21,10 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; import { TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; + +jest.mock('../../../../common/hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; const mockSort: Sort[] = [ { @@ -88,6 +92,8 @@ describe('Body', () => { totalPages: 1, }; + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); + describe('rendering', () => { test('it renders the column headers', () => { const wrapper = mount( From 705feeb87ba2b1af692ac1e8ca428e4c8ecc55f3 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Thu, 22 Apr 2021 09:18:01 +0100 Subject: [PATCH 20/33] [ML] Indicate Apache and Nginx modules as legacy versions (#97801) * [ML] Indicate Apache and Nginx modules as legacy versions * [ML] Edits to module titles to capitalize Filebeat --- .../models/data_recognizer/modules/apache_ecs/manifest.json | 4 ++-- .../models/data_recognizer/modules/nginx_ecs/manifest.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json index c4b6b18f56bc8..e9cd932ce264c 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json @@ -1,7 +1,7 @@ { "id": "apache_ecs", - "title": "Apache access logs", - "description": "Find unusual activity in HTTP access logs from filebeat (ECS).", + "title": "Apache access logs (Filebeat)", + "description": "Legacy jobs for finding unusual activity in HTTP access logs. The latest versions are installed with the Apache integration in Fleet.", "type": "Web Access Logs", "logoFile": "logo.json", "defaultIndexPattern": "filebeat-*", diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json index c39e4e5e4faf6..e219035178941 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json @@ -1,7 +1,7 @@ { "id": "nginx_ecs", - "title": "Nginx access logs", - "description": "Find unusual activity in HTTP access logs from filebeat (ECS).", + "title": "Nginx access logs (Filebeat)", + "description": "Legacy jobs for finding unusual activity in HTTP access logs. The latest versions are installed with the Nginx integration in Fleet.", "type": "Web Access Logs", "logoFile": "logo.json", "defaultIndexPattern": "filebeat-*", From 87fa92bf3a69618943275dbc1a8e828dccc13723 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Thu, 22 Apr 2021 10:30:53 +0200 Subject: [PATCH 21/33] Fix broken advanced setting i18n (#97782) --- src/plugins/discover/server/plugin.ts | 4 ++-- src/plugins/discover/server/ui_settings.ts | 15 +++++++++++---- src/plugins/vis_type_timeseries/server/plugin.ts | 4 ++-- .../vis_type_timeseries/server/ui_settings.ts | 4 ++-- src/plugins/vis_type_vislib/server/plugin.ts | 4 ++-- src/plugins/vis_type_vislib/server/ui_settings.ts | 4 ++-- src/plugins/vis_type_xy/server/plugin.ts | 6 +++--- 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts index 0078aacd6dfd1..27cb3cec8be41 100644 --- a/src/plugins/discover/server/plugin.ts +++ b/src/plugins/discover/server/plugin.ts @@ -7,14 +7,14 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/server'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; import { capabilitiesProvider } from './capabilities_provider'; import { searchSavedObjectType } from './saved_objects'; export class DiscoverServerPlugin implements Plugin { public setup(core: CoreSetup) { core.capabilities.registerProvider(capabilitiesProvider); - core.uiSettings.register(uiSettings); + core.uiSettings.register(getUiSettings()); core.savedObjects.registerType(searchSavedObjectType); return {}; diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index 103a06965835e..5f361ba2711cb 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -27,7 +27,7 @@ import { MAX_DOC_FIELDS_DISPLAYED, } from '../common'; -export const uiSettings: Record = { +export const getUiSettings: () => Record = () => ({ [DEFAULT_COLUMNS_SETTING]: { name: i18n.translate('discover.advancedSettings.defaultColumnsTitle', { defaultMessage: 'Default columns', @@ -186,10 +186,17 @@ export const uiSettings: Record = { }, }, [SEARCH_FIELDS_FROM_SOURCE]: { - name: 'Read fields from _source', - description: `When enabled will load documents directly from \`_source\`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.`, + name: i18n.translate('discover.advancedSettings.discover.readFieldsFromSource', { + defaultMessage: 'Read fields from _source', + }), + description: i18n.translate( + 'discover.advancedSettings.discover.readFieldsFromSourceDescription', + { + defaultMessage: `When enabled will load documents directly from \`_source\`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.`, + } + ), value: false, category: ['discover'], schema: schema.boolean(), }, -}; +}); diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index 8bc752e944709..93fd39528e70e 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -24,7 +24,7 @@ import { PluginStart } from '../../data/server'; import { IndexPatternsService } from '../../data/common'; import { visDataRoutes } from './routes/vis'; import { fieldsRoutes } from './routes/fields'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesVisDataRequest, @@ -83,7 +83,7 @@ export class VisTypeTimeseriesPlugin implements Plugin { plugins: VisTypeTimeseriesPluginSetupDependencies ) { const logger = this.initializerContext.logger.get('visTypeTimeseries'); - core.uiSettings.register(uiSettings); + core.uiSettings.register(getUiSettings()); const config$ = this.initializerContext.config.create(); // Global config contains things like the ES shard timeout const globalConfig$ = this.initializerContext.config.legacy.globalConfig$; diff --git a/src/plugins/vis_type_timeseries/server/ui_settings.ts b/src/plugins/vis_type_timeseries/server/ui_settings.ts index 07d2355b22253..e61635058cee0 100644 --- a/src/plugins/vis_type_timeseries/server/ui_settings.ts +++ b/src/plugins/vis_type_timeseries/server/ui_settings.ts @@ -13,7 +13,7 @@ import { UiSettingsParams } from 'kibana/server'; import { MAX_BUCKETS_SETTING } from '../common/constants'; -export const uiSettings: Record = { +export const getUiSettings: () => Record = () => ({ [MAX_BUCKETS_SETTING]: { name: i18n.translate('visTypeTimeseries.advancedSettings.maxBucketsTitle', { defaultMessage: 'TSVB buckets limit', @@ -25,4 +25,4 @@ export const uiSettings: Record = { }), schema: schema.number(), }, -}; +}); diff --git a/src/plugins/vis_type_vislib/server/plugin.ts b/src/plugins/vis_type_vislib/server/plugin.ts index f670c81a73386..dfbb30a13dbb1 100644 --- a/src/plugins/vis_type_vislib/server/plugin.ts +++ b/src/plugins/vis_type_vislib/server/plugin.ts @@ -7,11 +7,11 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/server'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; export class VisTypeVislibServerPlugin implements Plugin { public setup(core: CoreSetup) { - core.uiSettings.register(uiSettings); + core.uiSettings.register(getUiSettings()); return {}; } diff --git a/src/plugins/vis_type_vislib/server/ui_settings.ts b/src/plugins/vis_type_vislib/server/ui_settings.ts index c0ef3f3b40e1d..bd4615e47fb6e 100644 --- a/src/plugins/vis_type_vislib/server/ui_settings.ts +++ b/src/plugins/vis_type_vislib/server/ui_settings.ts @@ -12,7 +12,7 @@ import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from 'kibana/server'; import { DIMMING_OPACITY_SETTING, HEATMAP_MAX_BUCKETS_SETTING } from '../common'; -export const uiSettings: Record = { +export const getUiSettings: () => Record = () => ({ // TODO: move this to vis_type_xy when vislib is removed // https://github.com/elastic/kibana/issues/56143 [DIMMING_OPACITY_SETTING]: { @@ -47,4 +47,4 @@ export const uiSettings: Record = { category: ['visualization'], schema: schema.number(), }, -}; +}); diff --git a/src/plugins/vis_type_xy/server/plugin.ts b/src/plugins/vis_type_xy/server/plugin.ts index a9e6020cf3ee8..08aefdeb836b0 100644 --- a/src/plugins/vis_type_xy/server/plugin.ts +++ b/src/plugins/vis_type_xy/server/plugin.ts @@ -13,7 +13,7 @@ import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server'; import { LEGACY_CHARTS_LIBRARY } from '../common'; -export const uiSettingsConfig: Record> = { +export const getUiSettingsConfig: () => Record> = () => ({ // TODO: Remove this when vis_type_vislib is removed // https://github.com/elastic/kibana/issues/56143 [LEGACY_CHARTS_LIBRARY]: { @@ -31,11 +31,11 @@ export const uiSettingsConfig: Record> = { category: ['visualization'], schema: schema.boolean(), }, -}; +}); export class VisTypeXyServerPlugin implements Plugin { public setup(core: CoreSetup) { - core.uiSettings.register(uiSettingsConfig); + core.uiSettings.register(getUiSettingsConfig()); return {}; } From b2a9dd12a5db3c6ce5545290f5ff9748085dd7bd Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Thu, 22 Apr 2021 13:23:51 +0300 Subject: [PATCH 22/33] [Search] Remove msearch from searchsource (#96936) * Move inspector adapter integration into search source * docs and ts * Move other bucket to search source * test ts + delete unused tabilfy function * hierarchical param in aggconfig. ts improvements more inspector tests * fix jest * separate inspect more tests * jest * inspector * Error handling and more tests * put the fun in functional tests * delete client side legacy msearch code * ts * override to sync search in search source * delete more legacy code * ts * delete moarrrr --- .../create_search_source.test.ts | 5 - .../search/search_source/fetch/types.ts | 6 - .../search_source/legacy/call_client.test.ts | 102 -------------- .../search_source/legacy/call_client.ts | 38 ----- .../legacy/default_search_strategy.test.ts | 61 -------- .../legacy/default_search_strategy.ts | 63 --------- .../search_source/legacy/fetch_soon.test.ts | 132 ------------------ .../search/search_source/legacy/fetch_soon.ts | 83 ----------- .../search/search_source/legacy/index.ts | 1 - .../search/search_source/legacy/types.ts | 26 ---- .../data/common/search/search_source/mocks.ts | 6 +- .../search_source/search_source.test.ts | 30 ++-- .../search/search_source/search_source.ts | 35 ++--- .../search_source_service.test.ts | 5 - src/plugins/data/public/public.api.md | 3 +- .../public/search/legacy/call_msearch.test.ts | 43 ------ .../data/public/search/legacy/call_msearch.ts | 26 ---- .../data/public/search/legacy/index.ts | 9 -- .../data/public/search/search_service.ts | 9 +- .../data/server/search/search_service.ts | 12 +- src/plugins/data/server/server.api.md | 2 - src/plugins/data/server/ui_settings.ts | 4 +- 22 files changed, 33 insertions(+), 668 deletions(-) delete mode 100644 src/plugins/data/common/search/search_source/legacy/call_client.test.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/call_client.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/fetch_soon.ts delete mode 100644 src/plugins/data/public/search/legacy/call_msearch.test.ts delete mode 100644 src/plugins/data/public/search/legacy/call_msearch.ts delete mode 100644 src/plugins/data/public/search/legacy/index.ts diff --git a/src/plugins/data/common/search/search_source/create_search_source.test.ts b/src/plugins/data/common/search/search_source/create_search_source.test.ts index df31719b0aec4..6a6ac1dfa93e7 100644 --- a/src/plugins/data/common/search/search_source/create_search_source.test.ts +++ b/src/plugins/data/common/search/search_source/create_search_source.test.ts @@ -11,7 +11,6 @@ import { SearchSourceDependencies } from './search_source'; import { IIndexPattern } from '../../index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { Filter } from '../../es_query/filters'; -import { BehaviorSubject } from 'rxjs'; describe('createSearchSource', () => { const indexPatternMock: IIndexPattern = {} as IIndexPattern; @@ -24,10 +23,6 @@ describe('createSearchSource', () => { getConfig: jest.fn(), search: jest.fn(), onResponse: (req, res) => res, - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }; indexPatternContractMock = ({ diff --git a/src/plugins/data/common/search/search_source/fetch/types.ts b/src/plugins/data/common/search/search_source/fetch/types.ts index 8e8a9f1025b80..79aa45163b913 100644 --- a/src/plugins/data/common/search/search_source/fetch/types.ts +++ b/src/plugins/data/common/search/search_source/fetch/types.ts @@ -7,7 +7,6 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { LegacyFetchHandlers } from '../legacy/types'; import { GetConfigFn } from '../../../types'; /** @@ -29,11 +28,6 @@ export interface FetchHandlers { request: SearchRequest, response: estypes.SearchResponse ) => estypes.SearchResponse; - /** - * These handlers are only used by the legacy defaultSearchStrategy and can be removed - * once that strategy has been deprecated. - */ - legacy: LegacyFetchHandlers; } export interface SearchError { diff --git a/src/plugins/data/common/search/search_source/legacy/call_client.test.ts b/src/plugins/data/common/search/search_source/legacy/call_client.test.ts deleted file mode 100644 index 93849b63939e4..0000000000000 --- a/src/plugins/data/common/search/search_source/legacy/call_client.test.ts +++ /dev/null @@ -1,102 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { callClient } from './call_client'; -import { SearchStrategySearchParams } from './types'; -import { defaultSearchStrategy } from './default_search_strategy'; -import { FetchHandlers } from '../fetch'; -import { BehaviorSubject } from 'rxjs'; - -const mockAbortFn = jest.fn(); - -jest.mock('./default_search_strategy', () => { - return { - defaultSearchStrategy: { - search: jest.fn(({ searchRequests }: SearchStrategySearchParams) => { - return { - searching: Promise.resolve( - searchRequests.map((req) => { - return { - id: req._searchStrategyId, - }; - }) - ), - abort: mockAbortFn, - }; - }), - }, - }; -}); - -describe('callClient', () => { - const handleResponse = jest.fn().mockImplementation((req, res) => res); - const handlers = { - getConfig: jest.fn(), - onResponse: handleResponse, - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, - } as FetchHandlers; - - beforeEach(() => { - handleResponse.mockClear(); - }); - - test('Passes the additional arguments it is given to the search strategy', () => { - const searchRequests = [{ _searchStrategyId: 0 }]; - - callClient(searchRequests, [], handlers); - - expect(defaultSearchStrategy.search).toBeCalled(); - expect((defaultSearchStrategy.search as any).mock.calls[0][0]).toEqual({ - searchRequests, - ...handlers, - }); - }); - - test('Returns the responses in the original order', async () => { - const searchRequests = [{ _searchStrategyId: 1 }, { _searchStrategyId: 0 }]; - - const responses = await Promise.all(callClient(searchRequests, [], handlers)); - - expect(responses[0]).toEqual({ id: searchRequests[0]._searchStrategyId }); - expect(responses[1]).toEqual({ id: searchRequests[1]._searchStrategyId }); - }); - - test('Calls handleResponse with each request and response', async () => { - const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }]; - - const responses = callClient(searchRequests, [], handlers); - await Promise.all(responses); - - expect(handleResponse).toBeCalledTimes(2); - expect(handleResponse).toBeCalledWith(searchRequests[0], { - id: searchRequests[0]._searchStrategyId, - }); - expect(handleResponse).toBeCalledWith(searchRequests[1], { - id: searchRequests[1]._searchStrategyId, - }); - }); - - test('If passed an abortSignal, calls abort on the strategy if the signal is aborted', () => { - const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }]; - const abortController = new AbortController(); - const requestOptions = [ - { - abortSignal: abortController.signal, - }, - ]; - - callClient(searchRequests, requestOptions, handlers); - abortController.abort(); - - expect(mockAbortFn).toBeCalled(); - // expect(mockAbortFns[1]).not.toBeCalled(); - }); -}); diff --git a/src/plugins/data/common/search/search_source/legacy/call_client.ts b/src/plugins/data/common/search/search_source/legacy/call_client.ts deleted file mode 100644 index 4c1156aac7015..0000000000000 --- a/src/plugins/data/common/search/search_source/legacy/call_client.ts +++ /dev/null @@ -1,38 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { estypes } from '@elastic/elasticsearch'; -import { FetchHandlers, SearchRequest } from '../fetch'; -import { defaultSearchStrategy } from './default_search_strategy'; -import { ISearchOptions } from '../../index'; - -export function callClient( - searchRequests: SearchRequest[], - requestsOptions: ISearchOptions[] = [], - fetchHandlers: FetchHandlers -) { - // Correlate the options with the request that they're associated with - const requestOptionEntries: Array< - [SearchRequest, ISearchOptions] - > = searchRequests.map((request, i) => [request, requestsOptions[i]]); - const requestOptionsMap = new Map(requestOptionEntries); - const requestResponseMap = new Map>>(); - - const { searching, abort } = defaultSearchStrategy.search({ - searchRequests, - ...fetchHandlers, - }); - - searchRequests.forEach((request, i) => { - const response = searching.then((results) => fetchHandlers.onResponse(request, results[i])); - const { abortSignal = null } = requestOptionsMap.get(request) || {}; - if (abortSignal) abortSignal.addEventListener('abort', abort); - requestResponseMap.set(request, response); - }); - return searchRequests.map((request) => requestResponseMap.get(request)!); -} diff --git a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts b/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts deleted file mode 100644 index 9b03ebdbd116f..0000000000000 --- a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts +++ /dev/null @@ -1,61 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { MockedKeys } from '@kbn/utility-types/jest'; -import { defaultSearchStrategy } from './default_search_strategy'; -import { LegacyFetchHandlers, SearchStrategySearchParams } from './types'; -import { BehaviorSubject } from 'rxjs'; - -const { search } = defaultSearchStrategy; - -describe('defaultSearchStrategy', () => { - describe('search', () => { - let searchArgs: MockedKeys; - - beforeEach(() => { - searchArgs = { - searchRequests: [ - { - index: { title: 'foo' }, - body: {}, - }, - ], - getConfig: jest.fn(), - onResponse: (req, res) => res, - legacy: { - callMsearch: jest.fn().mockResolvedValue(undefined), - loadingCount$: new BehaviorSubject(0) as any, - } as jest.Mocked, - }; - }); - - test('calls callMsearch with the correct arguments', async () => { - await search({ ...searchArgs }); - expect(searchArgs.legacy.callMsearch.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "body": Object { - "searches": Array [ - Object { - "body": Object {}, - "header": Object { - "index": "foo", - "preference": undefined, - }, - }, - ], - }, - "signal": AbortSignal {}, - }, - ], - ] - `); - }); - }); -}); diff --git a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts b/src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts deleted file mode 100644 index 16e109d65a5be..0000000000000 --- a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts +++ /dev/null @@ -1,63 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getPreference } from '../fetch'; -import { SearchStrategyProvider, SearchStrategySearchParams } from './types'; - -// @deprecated -export const defaultSearchStrategy: SearchStrategyProvider = { - id: 'default', - - search: (params) => { - return msearch(params); - }, -}; - -function msearch({ searchRequests, getConfig, legacy }: SearchStrategySearchParams) { - const { callMsearch, loadingCount$ } = legacy; - - const requests = searchRequests.map(({ index, body }) => { - return { - header: { - index: index.title || index, - preference: getPreference(getConfig), - }, - body, - }; - }); - - const abortController = new AbortController(); - let resolved = false; - - // Start LoadingIndicator - loadingCount$.next(loadingCount$.getValue() + 1); - - const cleanup = () => { - if (!resolved) { - resolved = true; - // Decrement loading counter & cleanup BehaviorSubject - loadingCount$.next(loadingCount$.getValue() - 1); - loadingCount$.complete(); - } - }; - - const searching = callMsearch({ - body: { searches: requests }, - signal: abortController.signal, - }) - .then((res: any) => res?.body?.responses) - .finally(() => cleanup()); - - return { - abort: () => { - abortController.abort(); - cleanup(); - }, - searching, - }; -} diff --git a/src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts b/src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts deleted file mode 100644 index eca6e75fc69de..0000000000000 --- a/src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts +++ /dev/null @@ -1,132 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SearchResponse } from 'elasticsearch'; -import { UI_SETTINGS } from '../../../constants'; -import { GetConfigFn } from '../../../types'; -import { FetchHandlers, SearchRequest } from '../fetch'; -import { ISearchOptions } from '../../index'; -import { callClient } from './call_client'; -import { fetchSoon } from './fetch_soon'; - -function getConfigStub(config: any = {}): GetConfigFn { - return (key) => config[key]; -} - -const mockResponses: Record> = { - foo: { - took: 1, - timed_out: false, - } as SearchResponse, - bar: { - took: 2, - timed_out: false, - } as SearchResponse, - baz: { - took: 3, - timed_out: false, - } as SearchResponse, -}; - -jest.useFakeTimers(); - -jest.mock('./call_client', () => ({ - callClient: jest.fn((requests: SearchRequest[]) => { - // Allow a request object to specify which mockResponse it wants to receive (_mockResponseId) - // in addition to how long to simulate waiting before returning a response (_waitMs) - const responses = requests.map((request) => { - const waitMs = requests.reduce((total, { _waitMs }) => total + _waitMs || 0, 0); - return new Promise((resolve) => { - setTimeout(() => { - resolve(mockResponses[request._mockResponseId]); - }, waitMs); - }); - }); - return Promise.resolve(responses); - }), -})); - -describe('fetchSoon', () => { - beforeEach(() => { - (callClient as jest.Mock).mockClear(); - }); - - test('should execute asap if config is set to not batch searches', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false }); - const request = {}; - const options = {}; - - fetchSoon(request, options, { getConfig } as FetchHandlers); - - expect(callClient).toBeCalled(); - }); - - test('should delay by 50ms if config is set to batch searches', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const request = {}; - const options = {}; - - fetchSoon(request, options, { getConfig } as FetchHandlers); - - expect(callClient).not.toBeCalled(); - jest.advanceTimersByTime(0); - expect(callClient).not.toBeCalled(); - jest.advanceTimersByTime(50); - expect(callClient).toBeCalled(); - }); - - test('should send a batch of requests to callClient', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const requests = [{ foo: 1 }, { foo: 2 }]; - const options = [{ bar: 1 }, { bar: 2 }]; - - requests.forEach((request, i) => { - fetchSoon(request, options[i] as ISearchOptions, { getConfig } as FetchHandlers); - }); - - jest.advanceTimersByTime(50); - expect(callClient).toBeCalledTimes(1); - expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(requests); - expect((callClient as jest.Mock).mock.calls[0][1]).toEqual(options); - }); - - test('should return the response to the corresponding call for multiple batched requests', async () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }]; - - const promises = requests.map((request) => { - return fetchSoon(request, {}, { getConfig } as FetchHandlers); - }); - jest.advanceTimersByTime(50); - const results = await Promise.all(promises); - - expect(results).toEqual([mockResponses.foo, mockResponses.bar]); - }); - - test('should wait for the previous batch to start before starting a new batch', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const firstBatch = [{ foo: 1 }, { foo: 2 }]; - const secondBatch = [{ bar: 1 }, { bar: 2 }]; - - firstBatch.forEach((request) => { - fetchSoon(request, {}, { getConfig } as FetchHandlers); - }); - jest.advanceTimersByTime(50); - secondBatch.forEach((request) => { - fetchSoon(request, {}, { getConfig } as FetchHandlers); - }); - - expect(callClient).toBeCalledTimes(1); - expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(firstBatch); - - jest.advanceTimersByTime(50); - - expect(callClient).toBeCalledTimes(2); - expect((callClient as jest.Mock).mock.calls[1][0]).toEqual(secondBatch); - }); -}); diff --git a/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts b/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts deleted file mode 100644 index ff8ae2d19bd56..0000000000000 --- a/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts +++ /dev/null @@ -1,83 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { estypes } from '@elastic/elasticsearch'; -import { UI_SETTINGS } from '../../../constants'; -import { FetchHandlers, SearchRequest } from '../fetch'; -import { ISearchOptions } from '../../index'; -import { callClient } from './call_client'; - -/** - * This function introduces a slight delay in the request process to allow multiple requests to queue - * up (e.g. when a dashboard is loading). - */ -export async function fetchSoon( - request: SearchRequest, - options: ISearchOptions, - fetchHandlers: FetchHandlers -) { - const msToDelay = fetchHandlers.getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0; - return delayedFetch(request, options, fetchHandlers, msToDelay); -} - -/** - * Delays executing a function for a given amount of time, and returns a promise that resolves - * with the result. - * @param fn The function to invoke - * @param ms The number of milliseconds to wait - * @return Promise A promise that resolves with the result of executing the function - */ -function delay(fn: (...args: any) => T, ms: number): Promise { - return new Promise((resolve) => { - setTimeout(() => resolve(fn()), ms); - }); -} - -// The current batch/queue of requests to fetch -let requestsToFetch: SearchRequest[] = []; -let requestOptions: ISearchOptions[] = []; - -// The in-progress fetch (if there is one) -let fetchInProgress: any = null; - -/** - * Delay fetching for a given amount of time, while batching up the requests to be fetched. - * Returns a promise that resolves with the response for the given request. - * @param request The request to fetch - * @param ms The number of milliseconds to wait (and batch requests) - * @return Promise The response for the given request - */ -async function delayedFetch( - request: SearchRequest, - options: ISearchOptions, - fetchHandlers: FetchHandlers, - ms: number -): Promise> { - if (ms === 0) { - return callClient([request], [options], fetchHandlers)[0] as Promise< - estypes.SearchResponse - >; - } - - const i = requestsToFetch.length; - requestsToFetch = [...requestsToFetch, request]; - requestOptions = [...requestOptions, options]; - - // Note: the typescript here only worked because `SearchResponse` was `any` - // Since this code is legacy, I'm leaving the any here. - const responses: any[] = await (fetchInProgress = - fetchInProgress || - delay(() => { - const response = callClient(requestsToFetch, requestOptions, fetchHandlers); - requestsToFetch = []; - requestOptions = []; - fetchInProgress = null; - return response; - }, ms)); - return responses[i]; -} diff --git a/src/plugins/data/common/search/search_source/legacy/index.ts b/src/plugins/data/common/search/search_source/legacy/index.ts index 2c90dc6795423..12594660136d8 100644 --- a/src/plugins/data/common/search/search_source/legacy/index.ts +++ b/src/plugins/data/common/search/search_source/legacy/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export { fetchSoon } from './fetch_soon'; export * from './types'; diff --git a/src/plugins/data/common/search/search_source/legacy/types.ts b/src/plugins/data/common/search/search_source/legacy/types.ts index a4328528fd662..6778be77c21c5 100644 --- a/src/plugins/data/common/search/search_source/legacy/types.ts +++ b/src/plugins/data/common/search/search_source/legacy/types.ts @@ -6,9 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject } from 'rxjs'; import type { estypes, ApiResponse } from '@elastic/elasticsearch'; -import { FetchHandlers, SearchRequest } from '../fetch'; interface MsearchHeaders { index: string; @@ -29,27 +27,3 @@ export interface MsearchRequestBody { export interface MsearchResponse { body: ApiResponse<{ responses: Array> }>; } - -// @internal -export interface LegacyFetchHandlers { - callMsearch: (params: { - body: MsearchRequestBody; - signal: AbortSignal; - }) => Promise; - loadingCount$: BehaviorSubject; -} - -export interface SearchStrategySearchParams extends FetchHandlers { - searchRequests: SearchRequest[]; -} - -// @deprecated -export interface SearchStrategyProvider { - id: string; - search: (params: SearchStrategySearchParams) => SearchStrategyResponse; -} - -export interface SearchStrategyResponse { - searching: Promise>>; - abort: () => void; -} diff --git a/src/plugins/data/common/search/search_source/mocks.ts b/src/plugins/data/common/search/search_source/mocks.ts index ade22c20596d9..64ed82f36e81b 100644 --- a/src/plugins/data/common/search/search_source/mocks.ts +++ b/src/plugins/data/common/search/search_source/mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject, of } from 'rxjs'; +import { of } from 'rxjs'; import type { MockedKeys } from '@kbn/utility-types/jest'; import { uiSettingsServiceMock } from '../../../../../core/public/mocks'; @@ -47,8 +47,4 @@ export const createSearchSourceMock = (fields?: SearchSourceFields) => getConfig: uiSettingsServiceMock.createStartContract().get, search: jest.fn(), onResponse: jest.fn().mockImplementation((req, res) => res), - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }); diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 012fc5257397b..68e386acfd48c 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -6,20 +6,15 @@ * Side Public License, v 1. */ -import { BehaviorSubject, of } from 'rxjs'; +import { of } from 'rxjs'; import { IndexPattern } from '../../index_patterns'; import { GetConfigFn } from '../../types'; -import { fetchSoon } from './legacy'; import { SearchSource, SearchSourceDependencies, SortDirection } from './'; -import { AggConfigs, AggTypesRegistryStart } from '../../'; +import { AggConfigs, AggTypesRegistryStart, ES_SEARCH_STRATEGY } from '../../'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { RequestResponder } from 'src/plugins/inspector/common'; import { switchMap } from 'rxjs/operators'; -jest.mock('./legacy', () => ({ - fetchSoon: jest.fn().mockResolvedValue({}), -})); - const getComputedFields = () => ({ storedFields: [], scriptFields: {}, @@ -89,10 +84,6 @@ describe('SearchSource', () => { getConfig: getConfigMock, search: mockSearchMethod, onResponse: (req, res) => res, - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }; searchSource = new SearchSource({}, searchSourceDependencies); @@ -869,7 +860,7 @@ describe('SearchSource', () => { }); describe('fetch$', () => { - describe('#legacy fetch()', () => { + describe('#legacy COURIER_BATCH_SEARCHES', () => { beforeEach(() => { searchSourceDependencies = { ...searchSourceDependencies, @@ -879,11 +870,22 @@ describe('SearchSource', () => { }; }); - test('should call msearch', async () => { + test('should override to use sync search if not set', async () => { searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = {}; await searchSource.fetch$(options).toPromise(); - expect(fetchSoon).toBeCalledTimes(1); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe(ES_SEARCH_STRATEGY); + }); + + test('should not override strategy if set ', async () => { + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + const options = { strategy: 'banana' }; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe('banana'); }); }); diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 6f34d5ce1f29c..585126e1184d2 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -75,7 +75,7 @@ import { estypes } from '@elastic/elasticsearch'; import { normalizeSortRequest } from './normalize_sort_request'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; import { IIndexPattern, IndexPattern, IndexPatternField } from '../../index_patterns'; -import { AggConfigs, ISearchGeneric, ISearchOptions } from '../..'; +import { AggConfigs, ES_SEARCH_STRATEGY, ISearchGeneric, ISearchOptions } from '../..'; import type { ISearchSource, SearchFieldValue, @@ -95,7 +95,6 @@ import { IKibanaSearchResponse, } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; -import { fetchSoon } from './legacy'; import { extractReferences } from './extract_references'; /** @internal */ @@ -274,6 +273,13 @@ export class SearchSource { */ fetch$(options: ISearchOptions = {}) { const { getConfig } = this.dependencies; + const syncSearchByDefault = getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES); + + // Use the sync search strategy if legacy search is enabled. + // This still uses bfetch for batching. + if (!options?.strategy && syncSearchByDefault) { + options.strategy = ES_SEARCH_STRATEGY; + } const s$ = defer(() => this.requestIsStarting(options)).pipe( switchMap(() => { @@ -283,9 +289,7 @@ export class SearchSource { options.indexPattern = searchRequest.index; } - return getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES) - ? from(this.legacyFetch(searchRequest, options)) - : this.fetchSearch$(searchRequest, options); + return this.fetchSearch$(searchRequest, options); }), tap((response) => { // TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved @@ -477,27 +481,6 @@ export class SearchSource { ); } - /** - * Run a search using the search service - * @return {Promise>} - */ - private async legacyFetch(searchRequest: SearchRequest, options: ISearchOptions) { - const { getConfig, legacy, onResponse } = this.dependencies; - - return await fetchSoon( - searchRequest, - { - ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), - ...options, - }, - { - getConfig, - onResponse, - legacy, - } - ); - } - /** * Called by requests of this search source when they are started * @param options diff --git a/src/plugins/data/common/search/search_source/search_source_service.test.ts b/src/plugins/data/common/search/search_source/search_source_service.test.ts index 9e36d3c6002da..23bb809092bde 100644 --- a/src/plugins/data/common/search/search_source/search_source_service.test.ts +++ b/src/plugins/data/common/search/search_source/search_source_service.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { BehaviorSubject } from 'rxjs'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { SearchSourceService, SearchSourceDependencies } from './'; @@ -19,10 +18,6 @@ describe('SearchSource service', () => { getConfig: jest.fn(), search: jest.fn(), onResponse: jest.fn(), - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }; }); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 820619aa05ed8..cc7228268cb7a 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -7,8 +7,7 @@ import { $Values } from '@kbn/utility-types'; import { Action } from 'history'; import { Adapters as Adapters_2 } from 'src/plugins/inspector/common'; -import { ApiResponse } from '@elastic/elasticsearch'; -import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch/lib/Transport'; +import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { BehaviorSubject } from 'rxjs'; diff --git a/src/plugins/data/public/search/legacy/call_msearch.test.ts b/src/plugins/data/public/search/legacy/call_msearch.test.ts deleted file mode 100644 index 0627a09e12e67..0000000000000 --- a/src/plugins/data/public/search/legacy/call_msearch.test.ts +++ /dev/null @@ -1,43 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { HttpStart } from 'src/core/public'; -import { coreMock } from '../../../../../core/public/mocks'; -import { getCallMsearch } from './call_msearch'; - -describe('callMsearch', () => { - const msearchMock = jest.fn().mockResolvedValue({ body: { responses: [] } }); - let http: jest.Mocked; - - beforeEach(() => { - msearchMock.mockClear(); - http = coreMock.createStart().http; - http.post.mockResolvedValue(msearchMock); - }); - - test('calls http.post with the correct arguments', async () => { - const searches = [{ header: { index: 'foo' }, body: {} }]; - const callMsearch = getCallMsearch({ http }); - await callMsearch({ - body: { searches }, - signal: new AbortController().signal, - }); - - expect(http.post.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "/internal/_msearch", - Object { - "body": "{\\"searches\\":[{\\"header\\":{\\"index\\":\\"foo\\"},\\"body\\":{}}]}", - "signal": AbortSignal {}, - }, - ], - ] - `); - }); -}); diff --git a/src/plugins/data/public/search/legacy/call_msearch.ts b/src/plugins/data/public/search/legacy/call_msearch.ts deleted file mode 100644 index f20ae322fee57..0000000000000 --- a/src/plugins/data/public/search/legacy/call_msearch.ts +++ /dev/null @@ -1,26 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { HttpStart } from 'src/core/public'; -import { LegacyFetchHandlers } from '../../../common/search/search_source'; - -/** - * Wrapper for calling the internal msearch endpoint from the client. - * This is needed to abstract away differences in the http service - * between client & server. - * - * @internal - */ -export function getCallMsearch({ http }: { http: HttpStart }): LegacyFetchHandlers['callMsearch'] { - return async ({ body, signal }) => { - return http.post('/internal/_msearch', { - body: JSON.stringify(body), - signal, - }); - }; -} diff --git a/src/plugins/data/public/search/legacy/index.ts b/src/plugins/data/public/search/legacy/index.ts deleted file mode 100644 index 52f576d1b2e34..0000000000000 --- a/src/plugins/data/public/search/legacy/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './call_msearch'; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 83a44b6f68af6..ec7a486445b71 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -35,7 +35,6 @@ import { phraseFilterFunction, esRawResponse, } from '../../common/search'; -import { getCallMsearch } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; import { IndexPatternsContract } from '../index_patterns/index_patterns'; import { ISearchInterceptor, SearchInterceptor } from './search_interceptor'; @@ -157,10 +156,10 @@ export class SearchService implements Plugin { } public start( - { application, http, notifications, uiSettings }: CoreStart, + { http, uiSettings }: CoreStart, { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { - const search = ((request, options) => { + const search = ((request, options = {}) => { return this.searchInterceptor.search(request, options); }) as ISearchGeneric; @@ -171,10 +170,6 @@ export class SearchService implements Plugin { getConfig: uiSettings.get.bind(uiSettings), search, onResponse: handleResponse, - legacy: { - callMsearch: getCallMsearch({ http }), - loadingCount$, - }, }; return { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 0201f3226fd38..383e09b4a6ebe 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject, from, Observable, throwError } from 'rxjs'; +import { from, Observable, throwError } from 'rxjs'; import { pick } from 'lodash'; import moment from 'moment'; import { @@ -36,7 +36,7 @@ import { AggsService } from './aggs'; import { FieldFormatsStart } from '../field_formats'; import { IndexPatternsServiceStart } from '../index_patterns'; -import { getCallMsearch, registerMsearchRoute, registerSearchRoute } from './routes'; +import { registerMsearchRoute, registerSearchRoute } from './routes'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './strategies/es_search'; import { DataPluginStart, DataPluginStartDependencies } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; @@ -237,14 +237,6 @@ export class SearchService implements Plugin { getConfig: (key: string): T => uiSettingsCache[key], search: this.asScoped(request).search, onResponse: (req, res) => res, - legacy: { - callMsearch: getCallMsearch({ - esClient, - globalConfig$: this.initializerContext.config.legacy.globalConfig$, - uiSettings: uiSettingsClient, - }), - loadingCount$: new BehaviorSubject(0), - }, }; return this.searchSourceService.start(scopedIndexPatterns, searchSourceDependencies); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index be502950a84e3..15d3f5c403b1f 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -6,9 +6,7 @@ import { $Values } from '@kbn/utility-types'; import { Adapters } from 'src/plugins/inspector/common'; -import { ApiResponse } from '@elastic/elasticsearch'; import { Assign } from '@kbn/utility-types'; -import { BehaviorSubject } from 'rxjs'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ConfigDeprecationProvider } from '@kbn/config'; import { CoreSetup } from 'src/core/server'; diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 971ae3bb7507b..78d7c15cac5d6 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -276,12 +276,12 @@ export function getUiSettings(): Record> { }, [UI_SETTINGS.COURIER_BATCH_SEARCHES]: { name: i18n.translate('data.advancedSettings.courier.batchSearchesTitle', { - defaultMessage: 'Use legacy search', + defaultMessage: 'Use sync search', }), value: false, type: 'boolean', description: i18n.translate('data.advancedSettings.courier.batchSearchesText', { - defaultMessage: `Kibana uses a new search and batching infrastructure. + defaultMessage: `Kibana uses a new asynchronous search and infrastructure. Enable this option if you prefer to fallback to the legacy synchronous behavior`, }), deprecation: { From fbc6eb733d49bf0a57aefde64370a511ef2d21ac Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 22 Apr 2021 13:12:21 +0200 Subject: [PATCH 23/33] [Ingest Pipelines] Fix descriptions not showing and minor appearance tweaks (#97799) * fix descriptions not showing and minor appearance tweaks * remove unnecessary prop * added jest test for presence of descriptions * refactored variable names Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pipeline_processors_editor.helpers.tsx | 2 + .../pipeline_processors_editor.test.tsx | 62 +++++++++++++++++++ .../inline_text_input.tsx | 5 +- .../pipeline_processors_editor_item.tsx | 17 +++-- .../processor_information.tsx | 16 +++-- 5 files changed, 90 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx index fabb6a46c4943..4677ea4b747b5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -92,9 +92,11 @@ const createActions = (testBed: TestBed) => { jsonContent: JSON.stringify(options), }); }); + component.update(); await act(async () => { find('addProcessorForm.submitButton').simulate('click'); }); + component.update(); }, removeProcessor(processorSelector: string) { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx index e89e91c1cbaa9..fbc46159c4e13 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx @@ -13,6 +13,7 @@ const testProcessors: Pick = { { script: { source: 'ctx._type = null', + description: 'my script', }, }, { @@ -252,5 +253,66 @@ describe('Pipeline Editor', () => { expect(data.processors).toEqual([testProcessors.processors[1], testProcessors.processors[2]]); expect(data.on_failure).toEqual([testProcessors.processors[0]]); }); + + it('shows user provided descriptions rather than default descriptions and default descriptions rather than no description', async () => { + const { actions, find } = testBed; + + await actions.addProcessor('processors', 'test', { if: '1 == 1' }); + + const processorDescriptions = { + userProvided: 'my script', + default: 'Sets value of "test" to "test"', + none: 'No description', + }; + + const createAssertForProcessor = (processorIndex: string) => ({ + description, + descriptionVisible, + }: { + description: string; + descriptionVisible: boolean; + }) => { + expect(find(`processors>${processorIndex}.inlineTextInputNonEditableText`).text()).toBe( + description + ); + expect( + (find(`processors>${processorIndex}.pipelineProcessorItemDescriptionContainer`).props() + .className as string).includes('--displayNone') + ).toBe(!descriptionVisible); + }; + + const assertScriptProcessor = createAssertForProcessor('0'); + const assertSetProcessor = createAssertForProcessor('2'); + const assertTestProcessor = createAssertForProcessor('3'); + + assertScriptProcessor({ + description: processorDescriptions.userProvided, + descriptionVisible: true, + }); + + assertSetProcessor({ + description: processorDescriptions.default, + descriptionVisible: true, + }); + + assertTestProcessor({ description: processorDescriptions.none, descriptionVisible: true }); + + // Enter "move" mode + find('processors>0.moveItemButton').simulate('click'); + + // We expect that descriptions remain exactly the same, but the processor with "No description" has + // its description hidden + assertScriptProcessor({ + description: processorDescriptions.userProvided, + descriptionVisible: true, + }); + + assertSetProcessor({ + description: processorDescriptions.default, + descriptionVisible: true, + }); + + assertTestProcessor({ description: processorDescriptions.none, descriptionVisible: false }); + }); }); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx index cd7c91685467a..f23442426d713 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx @@ -92,7 +92,10 @@ function _InlineTextInput({ > -
+
{text || {placeholder}}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx index 1ba883990ce18..4d3238bdb7e44 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx @@ -87,12 +87,16 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( 'pipelineProcessorsEditor__item--dimmed': isDimmed, }); + const defaultDescription = processorDescriptor?.getDefaultDescription(processor.options); + + const hasNoDescription = !defaultDescription && !processor.options.description; + const inlineTextInputContainerClasses = classNames( 'pipelineProcessorsEditor__item__descriptionContainer', { // eslint-disable-next-line @typescript-eslint/naming-convention 'pipelineProcessorsEditor__item__descriptionContainer--displayNone': - isInMoveMode && !processor.options.description, + isInMoveMode && hasNoDescription, } ); @@ -208,16 +212,17 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( - + diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx index 5dc50625546a9..7950313bedfa4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx @@ -15,17 +15,23 @@ interface Props { } export const ProcessorInformation: FunctionComponent = memo(({ processor }) => { - const label = getProcessorDescriptor(processor.type)?.label ?? processor.type; + const processorDescriptor = getProcessorDescriptor(processor.type); + const label = processorDescriptor?.label ?? processor.type; + const description = + processor.options.description ?? processorDescriptor?.getDefaultDescription(processor.options); + return ( - {label} + + {label} + - {processor.options.description ? ( + {description ? ( - - {processor.options.description} + + {description} ) : undefined} From c8bd4b0bf8a38b2a398535ce45e516d5b66c105c Mon Sep 17 00:00:00 2001 From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Date: Thu, 22 Apr 2021 12:24:35 +0100 Subject: [PATCH 24/33] Use create over update (#97818) --- x-pack/plugins/infra/server/lib/sources/sources.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts index 0016e4716725d..24b204665c014 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.ts @@ -160,12 +160,15 @@ export class InfraSources { ); const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration( - await savedObjectsClient.update( + // update() will perform a deep merge. We use create() with overwrite: true instead. mergeSourceConfiguration() + // ensures the correct and intended merging of properties. + await savedObjectsClient.create( infraSourceConfigurationSavedObjectName, - sourceId, pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any, { + id: sourceId, version, + overwrite: true, } ) ); From f094d9fdc23d0bc393c072b96e437cdefc12bc4f Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 22 Apr 2021 12:50:35 +0100 Subject: [PATCH 25/33] [Security Solution] Events integration test (#97519) * events integration test * add constants Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apis/security_solution/events.ts | 499 ++++++++++++++++++ .../apis/security_solution/index.js | 1 + 2 files changed, 500 insertions(+) create mode 100644 x-pack/test/api_integration/apis/security_solution/events.ts diff --git a/x-pack/test/api_integration/apis/security_solution/events.ts b/x-pack/test/api_integration/apis/security_solution/events.ts new file mode 100644 index 0000000000000..e5363ec57de06 --- /dev/null +++ b/x-pack/test/api_integration/apis/security_solution/events.ts @@ -0,0 +1,499 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { + Direction, + TimelineEventsQueries, +} from '../../../../plugins/security_solution/common/search_strategy'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const TO = '3000-01-01T00:00:00.000Z'; +const FROM = '2000-01-01T00:00:00.000Z'; + +// typical values that have to change after an update from "scripts/es_archiver" +const DATA_COUNT = 7; +const HOST_NAME = 'suricata-sensor-amsterdam'; +const TOTAL_COUNT = 96; +const EDGE_LENGTH = 25; +const ACTIVE_PAGE = 0; +const PAGE_SIZE = 25; +const LIMITED_PAGE_SIZE = 2; + +const FILTER_VALUE = { + bool: { + filter: [ + { + bool: { + should: [{ match_phrase: { 'host.name': HOST_NAME } }], + minimum_should_match: 1, + }, + }, + { + bool: { + filter: [ + { + bool: { + should: [{ range: { '@timestamp': { gte: FROM } } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ range: { '@timestamp': { lte: TO } } }], + minimum_should_match: 1, + }, + }, + ], + }, + }, + ], + }, +}; + +/** + * https://www.elastic.co/guide/en/elasticsearch/reference/7.12/search-fields.html#docvalue-fields + * Use the docvalue_fields parameter to get values for selected fields. + * This can be a good choice when returning a fairly small number of fields that support doc values, + * such as keywords and dates. + */ +const DOC_VALUE_FIELDS = [ + { + field: '@timestamp', + }, + { + field: 'agent.ephemeral_id', + }, + { + field: 'agent.id', + }, + { + field: 'agent.name', + }, + { + field: 'agent.type', + }, + { + field: 'agent.version', + }, + { + field: 'as.number', + }, + { + field: 'as.organization.name', + }, + { + field: 'client.address', + }, + { + field: 'client.as.number', + }, + { + field: 'client.as.organization.name', + }, + { + field: 'client.bytes', + format: 'bytes', + }, + { + field: 'client.domain', + }, + { + field: 'client.geo.city_name', + }, + { + field: 'client.geo.continent_name', + }, + { + field: 'client.geo.country_iso_code', + }, + { + field: 'client.geo.country_name', + }, + { + field: 'client.geo.location', + }, + { + field: 'client.geo.name', + }, + { + field: 'client.geo.region_iso_code', + }, + { + field: 'client.geo.region_name', + }, + { + field: 'client.ip', + }, + { + field: 'client.mac', + }, + { + field: 'client.nat.ip', + }, + { + field: 'client.nat.port', + format: 'string', + }, + { + field: 'client.packets', + }, + { + field: 'client.port', + format: 'string', + }, + { + field: 'client.registered_domain', + }, + { + field: 'client.top_level_domain', + }, + { + field: 'client.user.domain', + }, + { + field: 'client.user.email', + }, + { + field: 'client.user.full_name', + }, + { + field: 'client.user.group.domain', + }, + { + field: 'client.user.group.id', + }, + { + field: 'client.user.group.name', + }, + { + field: 'client.user.hash', + }, + { + field: 'client.user.id', + }, + { + field: 'client.user.name', + }, + { + field: 'cloud.account.id', + }, + { + field: 'cloud.availability_zone', + }, + { + field: 'cloud.instance.id', + }, + { + field: 'cloud.instance.name', + }, + { + field: 'cloud.machine.type', + }, + { + field: 'cloud.provider', + }, + { + field: 'cloud.region', + }, + { + field: 'code_signature.exists', + }, + { + field: 'code_signature.status', + }, + { + field: 'code_signature.subject_name', + }, + { + field: 'code_signature.trusted', + }, + { + field: 'code_signature.valid', + }, + { + field: 'container.id', + }, + { + field: 'container.image.name', + }, + { + field: 'container.image.tag', + }, + { + field: 'container.name', + }, + { + field: 'container.runtime', + }, + { + field: 'destination.address', + }, + { + field: 'destination.as.number', + }, + { + field: 'destination.as.organization.name', + }, + { + field: 'destination.bytes', + format: 'bytes', + }, + { + field: 'destination.domain', + }, + { + field: 'destination.geo.city_name', + }, + { + field: 'destination.geo.continent_name', + }, + { + field: 'destination.geo.country_iso_code', + }, + { + field: 'destination.geo.country_name', + }, + { + field: 'destination.geo.location', + }, + { + field: 'destination.geo.name', + }, + { + field: 'destination.geo.region_iso_code', + }, + { + field: 'destination.geo.region_name', + }, + { + field: 'destination.ip', + }, + { + field: 'destination.mac', + }, + { + field: 'destination.nat.ip', + }, + { + field: 'destination.nat.port', + format: 'string', + }, + { + field: 'destination.packets', + }, + { + field: 'destination.port', + format: 'string', + }, + { + field: 'destination.registered_domain', + }, + { + field: 'destination.top_level_domain', + }, + { + field: 'destination.user.domain', + }, + { + field: 'destination.user.email', + }, + { + field: 'destination.user.full_name', + }, + { + field: 'destination.user.group.domain', + }, + { + field: 'destination.user.group.id', + }, + { + field: 'destination.user.group.name', + }, + { + field: 'destination.user.hash', + }, + { + field: 'destination.user.id', + }, + { + field: 'destination.user.name', + }, + { + field: 'dll.code_signature.exists', + }, + { + field: 'dll.code_signature.status', + }, + { + field: 'dll.code_signature.subject_name', + }, + { + field: 'dll.code_signature.trusted', + }, + { + field: 'dll.code_signature.valid', + }, + { + field: 'dll.hash.md5', + }, + { + field: 'dll.hash.sha1', + }, + { + field: 'dll.hash.sha256', + }, + { + field: 'dll.hash.sha512', + }, + { + field: 'dll.name', + }, + { + field: 'dll.path', + }, + { + field: 'dll.pe.company', + }, + { + field: 'dll.pe.description', + }, + { + field: 'dll.pe.file_version', + }, + { + field: 'dll.pe.original_file_name', + }, +]; +const FIELD_REQUESTED = [ + '@timestamp', + 'message', + 'event.category', + 'event.action', + 'host.name', + 'source.ip', + 'destination.ip', + 'user.name', + '@timestamp', + 'signal.status', + 'signal.group.id', + 'signal.original_time', + 'signal.rule.building_block_type', + 'signal.rule.filters', + 'signal.rule.from', + 'signal.rule.language', + 'signal.rule.query', + 'signal.rule.name', + 'signal.rule.to', + 'signal.rule.id', + 'signal.rule.index', + 'signal.rule.type', + 'signal.original_event.kind', + 'signal.original_event.module', + 'file.path', + 'file.Ext.code_signature.subject_name', + 'file.Ext.code_signature.trusted', + 'file.hash.sha256', + 'host.os.family', + 'event.code', +]; + +export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Timeline', () => { + before(() => esArchiver.load('auditbeat/hosts')); + after(() => esArchiver.unload('auditbeat/hosts')); + + it('Make sure that we get Timeline data', async () => { + await retry.try(async () => { + const resp = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .set('Content-Type', 'application/json') + .send({ + defaultIndex: ['auditbeat-*'], + docValueFields: DOC_VALUE_FIELDS, + factoryQueryType: TimelineEventsQueries.all, + fieldRequested: FIELD_REQUESTED, + fields: [], + filterQuery: FILTER_VALUE, + pagination: { + activePage: 0, + querySize: 25, + }, + language: 'kuery', + sort: [ + { + field: '@timestamp', + direction: Direction.desc, + type: 'number', + }, + ], + timerange: { + from: FROM, + to: TO, + interval: '12h', + }, + }) + .expect(200); + + const timeline = resp.body; + expect(timeline.edges.length).to.be(EDGE_LENGTH); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.totalCount).to.be(TOTAL_COUNT); + expect(timeline.pageInfo.activePage).to.equal(ACTIVE_PAGE); + expect(timeline.pageInfo.querySize).to.equal(PAGE_SIZE); + }); + }); + + it('Make sure that pagination is working in Timeline query', async () => { + await retry.try(async () => { + const resp = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .set('Content-Type', 'application/json') + .send({ + defaultIndex: ['auditbeat-*'], + docValueFields: DOC_VALUE_FIELDS, + factoryQueryType: TimelineEventsQueries.all, + fieldRequested: FIELD_REQUESTED, + fields: [], + filterQuery: FILTER_VALUE, + pagination: { + activePage: 0, + querySize: LIMITED_PAGE_SIZE, + }, + language: 'kuery', + sort: [ + { + field: '@timestamp', + direction: Direction.desc, + type: 'number', + }, + ], + timerange: { + from: FROM, + to: TO, + interval: '12h', + }, + }) + .expect(200); + + const timeline = resp.body; + expect(timeline.edges.length).to.be(LIMITED_PAGE_SIZE); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.totalCount).to.be(TOTAL_COUNT); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.edges[0]!.node.ecs.host!.name).to.eql([HOST_NAME]); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/security_solution/index.js b/x-pack/test/api_integration/apis/security_solution/index.js index 18c315a3b8c3d..3f9afba18b9ef 100644 --- a/x-pack/test/api_integration/apis/security_solution/index.js +++ b/x-pack/test/api_integration/apis/security_solution/index.js @@ -8,6 +8,7 @@ export default function ({ loadTestFile }) { describe('SecuritySolution Endpoints', () => { loadTestFile(require.resolve('./authentications')); + loadTestFile(require.resolve('./events')); loadTestFile(require.resolve('./hosts')); loadTestFile(require.resolve('./host_details')); loadTestFile(require.resolve('./kpi_network')); From 4295f8512461816aa6535e36496f40f6364aa572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 22 Apr 2021 14:22:42 +0200 Subject: [PATCH 26/33] [flaky-test] Unskip SOM edit_saved_object tests (#97846) --- .../apps/saved_objects_management/edit_saved_object.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 81569c5bfc498..89889088bd73b 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -55,8 +55,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await button.click(); }; - // Flaky: https://github.com/elastic/kibana/issues/68400 - describe.skip('saved objects edition page', () => { + describe('saved objects edition page', () => { beforeEach(async () => { await esArchiver.load('saved_objects_management/edit_saved_object'); }); From 649a2e01fc723f549b869607a0993af39a91174d Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 22 Apr 2021 08:01:45 -0500 Subject: [PATCH 27/33] [Workplace Search] PR#3358 to Kibana (#97921) * Render flash message when server sends back an error * Fetch data on all route changes This produced a bug where the nav and loading states were triggered between route changes. Added conditional to prevent resetting between in-source changes --- .../content_sources/source_logic.test.ts | 14 +++++++++++ .../views/content_sources/source_logic.ts | 6 +++++ .../content_sources/source_router.test.tsx | 24 ++++++++++++++++++- .../views/content_sources/source_router.tsx | 11 ++++++--- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index a9712cc4e1dc0..2cf867446b7fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -33,6 +33,7 @@ describe('SourceLogic', () => { flashAPIErrors, setSuccessMessage, setQueuedSuccessMessage, + setErrorMessage, } = mockFlashMessageHelpers; const { navigateToUrl } = mockKibanaValues; const { mount, getListeners } = new LogicMounter(SourceLogic); @@ -204,6 +205,19 @@ describe('SourceLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith(NOT_FOUND_PATH); }); + + it('renders error messages passed in success response from server', async () => { + const errors = ['ERROR']; + const promise = Promise.resolve({ + ...contentSource, + errors, + }); + http.get.mockReturnValue(promise); + SourceLogic.actions.initializeSource(contentSource.id); + await promise; + + expect(setErrorMessage).toHaveBeenCalledWith(errors); + }); }); describe('initializeFederatedSummary', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index ff3e1e83925d0..2e6a3c65597ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -13,6 +13,7 @@ import { DEFAULT_META } from '../../../shared/constants'; import { flashAPIErrors, setSuccessMessage, + setErrorMessage, setQueuedSuccessMessage, clearFlashMessages, } from '../../../shared/flash_messages'; @@ -148,6 +149,11 @@ export const SourceLogic = kea>({ if (response.isFederatedSource) { actions.initializeFederatedSummary(sourceId); } + if (response.errors) { + setErrorMessage(response.errors); + } else { + clearFlashMessages(); + } } catch (e) { if (e.response.status === 404) { KibanaLogic.values.navigateToUrl(NOT_FOUND_PATH); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx index 463468d1304b6..528065da23af6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx @@ -8,6 +8,8 @@ import '../../../__mocks__/shallow_useeffect.mock'; import { setMockValues, setMockActions } from '../../../__mocks__'; +import { mockLocation } from '../../../__mocks__/react_router_history.mock'; +import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; import { contentSources } from '../../__mocks__/content_sources.mock'; import React from 'react'; @@ -30,6 +32,7 @@ import { SourceRouter } from './source_router'; describe('SourceRouter', () => { const initializeSource = jest.fn(); + const resetSourceState = jest.fn(); const contentSource = contentSources[1]; const customSource = contentSources[0]; const mockValues = { @@ -40,10 +43,11 @@ describe('SourceRouter', () => { beforeEach(() => { setMockActions({ initializeSource, + resetSourceState, }); setMockValues({ ...mockValues }); (useParams as jest.Mock).mockImplementationOnce(() => ({ - sourceId: '1', + sourceId: contentSource.id, })); }); @@ -114,4 +118,22 @@ describe('SourceRouter', () => { NAV.DISPLAY_SETTINGS, ]); }); + + describe('reset state', () => { + it('does not reset state when switching between source tree views', () => { + mockLocation.pathname = `/sources/${contentSource.id}`; + shallow(); + unmountHandler(); + + expect(resetSourceState).not.toHaveBeenCalled(); + }); + + it('resets state when leaving source tree', () => { + mockLocation.pathname = '/home'; + shallow(); + unmountHandler(); + + expect(resetSourceState).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index b14ea4ebd7a73..cd20e32def16d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -6,7 +6,8 @@ */ import React, { useEffect } from 'react'; -import { Route, Switch, useParams } from 'react-router-dom'; + +import { Route, Switch, useLocation, useParams } from 'react-router-dom'; import { useActions, useValues } from 'kea'; import moment from 'moment'; @@ -47,14 +48,18 @@ import { SourceLogic } from './source_logic'; export const SourceRouter: React.FC = () => { const { sourceId } = useParams() as { sourceId: string }; + const { pathname } = useLocation(); const { initializeSource, resetSourceState } = useActions(SourceLogic); const { contentSource, dataLoading } = useValues(SourceLogic); const { isOrganization } = useValues(AppLogic); useEffect(() => { initializeSource(sourceId); - return resetSourceState; - }, []); + return () => { + // We only want to reset the state when leaving the source section. Otherwise there is an unwanted flash of UI. + if (!pathname.includes(sourceId)) resetSourceState(); + }; + }, [pathname]); if (dataLoading) return ; From ff0276b6a21eee7fcb714f3a593eabd57786ac56 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Thu, 22 Apr 2021 15:14:17 +0200 Subject: [PATCH 28/33] Block value `0` for terms.min_doc_count aggregation (#97966) * Block value `0` for terms.min_doc_count aggregation * do the same for histogram --- .../aggregations/aggs_types/bucket_aggs.ts | 4 +- .../aggregations/aggs_types/schemas.test.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts index 1508cab69a048..599c32137c553 100644 --- a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts @@ -49,7 +49,7 @@ export const bucketAggsSchemas: Record = { histogram: s.object({ field: s.maybe(s.string()), interval: s.maybe(s.number()), - min_doc_count: s.maybe(s.number()), + min_doc_count: s.maybe(s.number({ min: 1 })), extended_bounds: s.maybe( s.object({ min: s.number(), @@ -78,7 +78,7 @@ export const bucketAggsSchemas: Record = { include: s.maybe(s.oneOf([s.string(), s.arrayOf(s.string())])), execution_hint: s.maybe(s.string()), missing: s.maybe(s.number()), - min_doc_count: s.maybe(s.number()), + min_doc_count: s.maybe(s.number({ min: 1 })), size: s.maybe(s.number()), show_term_doc_count_error: s.maybe(s.boolean()), order: s.maybe(s.oneOf([s.literal('asc'), s.literal('desc')])), diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts new file mode 100644 index 0000000000000..33f7ca12abc53 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { bucketAggsSchemas } from './bucket_aggs'; + +describe('bucket aggregation schemas', () => { + describe('terms aggregation schema', () => { + const schema = bucketAggsSchemas.terms; + + it('passes validation when using `1` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 1 })).not.toThrow(); + }); + + // see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#_minimum_document_count_4 + // Setting min_doc_count=0 will also return buckets for terms that didn’t match any hit, + // bypassing any filtering perform via `filter` or `query` + // causing a potential security issue as we can return values from other spaces. + it('throws an error when using `0` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 0 })).toThrowErrorMatchingInlineSnapshot( + `"[min_doc_count]: Value must be equal to or greater than [1]."` + ); + }); + }); + + describe('histogram aggregation schema', () => { + const schema = bucketAggsSchemas.histogram; + + it('passes validation when using `1` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 1 })).not.toThrow(); + }); + + it('throws an error when using `0` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 0 })).toThrowErrorMatchingInlineSnapshot( + `"[min_doc_count]: Value must be equal to or greater than [1]."` + ); + }); + }); +}); From 158fff3297aafda63f193a2d50ee7d6dc2940b26 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 22 Apr 2021 07:51:30 -0600 Subject: [PATCH 29/33] [Maps] fix cannot read propery 'getImage' of undefined (#97829) --- .../connected_components/mb_map/mb_map.tsx | 29 +++++++++++++------ .../connected_components/mb_map/utils.js | 5 ---- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 66c9a2462736a..9ec6cbcb5d4ac 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -26,7 +26,7 @@ import { getInitialView } from './get_initial_view'; import { getPreserveDrawingBuffer } from '../../kibana_services'; import { ILayer } from '../../classes/layers/layer'; import { MapSettings } from '../../reducers/map'; -import { Goto } from '../../../common/descriptor_types'; +import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types'; import { DECIMAL_DEGREES_PRECISION, KBN_TOO_MANY_FEATURES_IMAGE_ID, @@ -35,8 +35,12 @@ import { } from '../../../common/constants'; import { getGlyphUrl, isRetina } from '../../util'; import { syncLayerOrder } from './sort_layers'; -// @ts-expect-error -import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils'; +import { + addSpriteSheetToMapFromImageData, + loadSpriteSheetImageData, + removeOrphanedSourcesAndLayers, + // @ts-expect-error +} from './utils'; import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { GeoFieldWithIndex } from '../../components/geo_field_with_index'; import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; @@ -172,8 +176,7 @@ export class MBMap extends Component { }; } - async _createMbMapInstance(): Promise { - const initialView = await getInitialView(this.props.goto, this.props.settings); + async _createMbMapInstance(initialView: MapCenterAndZoom | null): Promise { return new Promise((resolve) => { const mbStyle = { version: 8, @@ -237,9 +240,14 @@ export class MBMap extends Component { } async _initializeMap() { + const initialView = await getInitialView(this.props.goto, this.props.settings); + if (!this._isMounted) { + return; + } + let mbMap: MapboxMap; try { - mbMap = await this._createMbMapInstance(); + mbMap = await this._createMbMapInstance(initialView); } catch (error) { this.props.setMapInitError(error.message); return; @@ -293,10 +301,13 @@ export class MBMap extends Component { }); } - _loadMakiSprites(mbMap: MapboxMap) { - const sprites = isRetina() ? sprites2 : sprites1; + async _loadMakiSprites(mbMap: MapboxMap) { + const spritesUrl = isRetina() ? sprites2 : sprites1; const json = isRetina() ? spritesheet[2] : spritesheet[1]; - addSpritesheetToMap(json, sprites, mbMap); + const spritesData = await loadSpriteSheetImageData(spritesUrl); + if (this._isMounted) { + addSpriteSheetToMapFromImageData(json, spritesData, mbMap); + } } _syncMbMapWithMapState = () => { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js index f79f9bdffe366..5a2a98a24fca1 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/utils.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js @@ -51,11 +51,6 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLa mbSourcesToRemove.forEach((mbSourceId) => mbMap.removeSource(mbSourceId)); } -export async function addSpritesheetToMap(json, imgUrl, mbMap) { - const imgData = await loadSpriteSheetImageData(imgUrl); - addSpriteSheetToMapFromImageData(json, imgData, mbMap); -} - function getImageData(img) { const canvas = window.document.createElement('canvas'); const context = canvas.getContext('2d'); From fecdd45d93dc105264017bbe04dbf1f96102bcfb Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Thu, 22 Apr 2021 08:59:24 -0500 Subject: [PATCH 30/33] [ML] Fix Data Visualizer event rate chart empty for some indices when using long time range (#97655) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../content_types/document_count_content.tsx | 1 + .../document_count_chart.tsx | 22 ++++++++++++++++--- .../stats_table/types/field_vis_config.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx index 1fcc301fbdba7..588d85f24a023 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx @@ -41,6 +41,7 @@ export const DocumentCountContent: FC = ({ config, totalCount }) => { chartPoints={chartPoints} timeRangeEarliest={timeRangeEarliest} timeRangeLatest={timeRangeLatest} + interval={documentCounts.interval} /> ); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx index 4d0f432375330..4c8740cc76b6f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; @@ -29,6 +29,7 @@ interface Props { chartPoints: DocumentCountChartPoint[]; timeRangeEarliest: number; timeRangeLatest: number; + interval?: number; } const SPEC_ID = 'document_count'; @@ -38,6 +39,7 @@ export const DocumentCountChart: FC = ({ chartPoints, timeRangeEarliest, timeRangeLatest, + interval, }) => { const seriesName = i18n.translate('xpack.ml.fieldDataCard.documentCountChart.seriesLabel', { defaultMessage: 'document count', @@ -50,6 +52,21 @@ export const DocumentCountChart: FC = ({ const dateFormatter = niceTimeFormatter([timeRangeEarliest, timeRangeLatest]); + const adjustedChartPoints = useMemo(() => { + // Display empty chart when no data in range + if (chartPoints.length < 1) return [{ time: timeRangeEarliest, value: 0 }]; + + // If chart has only one bucket + // it won't show up correctly unless we add an extra data point + if (chartPoints.length === 1) { + return [ + ...chartPoints, + { time: interval ? Number(chartPoints[0].time) + interval : timeRangeEarliest, value: 0 }, + ]; + } + return chartPoints; + }, [chartPoints, timeRangeEarliest, timeRangeLatest, interval]); + return (
= ({ yScaleType={ScaleType.Linear} xAccessor="time" yAccessors={['value']} - // Display empty chart when no data in range - data={chartPoints.length > 0 ? chartPoints : [{ time: timeRangeEarliest, value: 0 }]} + data={adjustedChartPoints} />
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts index 0bf3b951f4246..aa7bd2f5ecf6d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts @@ -39,6 +39,7 @@ export interface FieldVisStats { latest?: number; documentCounts?: { buckets?: DocumentCountBuckets; + interval?: number; }; avg?: number; distribution?: { From 98c7d7da0a4806b47a13c156ec788caa4823cd32 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 22 Apr 2021 22:32:02 +0800 Subject: [PATCH 31/33] #95263 Set kbn-href when location changed (#95377) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/monitoring/public/angular/app_modules.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts index 8fa0629d013cf..71dc4919237e5 100644 --- a/x-pack/plugins/monitoring/public/angular/app_modules.ts +++ b/x-pack/plugins/monitoring/public/angular/app_modules.ts @@ -232,6 +232,11 @@ function createHrefModule(core: CoreStart) { $attr.$set('href', core.http.basePath.prepend(url)); } }); + + _$scope.$on('$locationChangeSuccess', () => { + const url = getSafeForExternalLink($attr.href as string); + $attr.$set('href', core.http.basePath.prepend(url)); + }); }, }, }; From 967b17275b4671de80aa79681284dd52df8162c2 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 22 Apr 2021 10:32:22 -0400 Subject: [PATCH 32/33] ensure progress is updated correctly (#97889) --- .../components/create_step_footer/create_step_footer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx index edbfc11343e38..3123a43594c93 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx @@ -83,10 +83,12 @@ export const CreateStepFooter: FC = ({ jobId, jobType, showProgress }) => } setCurrentProgress(progressStats); + // Clear if job is completed or stopped (after having started) if ( (progressStats.currentPhase === progressStats.totalPhases && progressStats.progress === 100) || - jobStats.state === DATA_FRAME_TASK_STATE.STOPPED + (jobStats.state === DATA_FRAME_TASK_STATE.STOPPED && + !(progressStats.currentPhase === 1 && progressStats.progress === 0)) ) { clearInterval(interval); // Check job has started. Jobs that fail to start will also have STOPPED state From 65287dffaddf6e6b481ad10c01bf2d3deeac6ee1 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 22 Apr 2021 09:43:53 -0500 Subject: [PATCH 33/33] [Enterprise Search] Fix unstyled UI for Schema Errors (#97776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor main component * Refactor faux view button The original design called for a button-looking component on the accordion header so it looks clickable to reveal the error underneath. Using a button element caused the console to error because the entire header is a button and this is a child. Adding an href fixes the error and allows for styling as a button * Use empty button rather than styles components for view button * Remove extra classNames and fix layout to stretch cells * Add stylesheet Without the width: 100% on .euiIEFlexWrapFix, the header table does not stretch across the screen. Couldn’t find another way around this and happy to take suggestions of a better idea. * Add prop to prevent line break on IDs Existing implementation was causing multi-character IDs to break to a new line. So for 12, its was: 1 2 --- .../schema/schema_errors_accordion.scss | 20 +++++++ .../schema/schema_errors_accordion.test.tsx | 6 +- .../shared/schema/schema_errors_accordion.tsx | 55 +++++++++---------- .../schema/schema_change_errors.tsx | 19 +++---- 4 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss new file mode 100644 index 0000000000000..e8e55ad2827c5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +.schemaFieldError { + border-top: 1px solid $euiColorLightShade; + + &:last-child { + border-bottom: 1px solid $euiColorLightShade; + } + + // Something about the EuiFlexGroup being inside a button collapses the row of items. + // This wrapper div was injected by EUI and had 'with: auto' on it. + .euiIEFlexWrapFix { + width: 100%; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx index a82f9e9b6113b..a15d39c447126 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx @@ -11,7 +11,7 @@ import { shallow } from 'enzyme'; import { EuiAccordion, EuiTableRow } from '@elastic/eui'; -import { EuiLinkTo } from '../react_router_helpers'; +import { EuiButtonEmptyTo } from '../react_router_helpers'; import { SchemaErrorsAccordion } from './schema_errors_accordion'; @@ -40,12 +40,12 @@ describe('SchemaErrorsAccordion', () => { expect(wrapper.find(EuiAccordion)).toHaveLength(1); expect(wrapper.find(EuiTableRow)).toHaveLength(2); - expect(wrapper.find(EuiLinkTo)).toHaveLength(0); + expect(wrapper.find(EuiButtonEmptyTo)).toHaveLength(0); }); it('renders document buttons', () => { const wrapper = shallow(); - expect(wrapper.find(EuiLinkTo)).toHaveLength(2); + expect(wrapper.find(EuiButtonEmptyTo)).toHaveLength(2); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx index c41781deafb95..09f499e540e93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiAccordion, + EuiButton, EuiFlexGroup, EuiFlexItem, EuiTable, @@ -19,10 +20,12 @@ import { EuiTableRowCell, } from '@elastic/eui'; -import { EuiLinkTo } from '../react_router_helpers'; +import { EuiButtonEmptyTo } from '../react_router_helpers'; import { TruncatedContent } from '../truncate'; +import './schema_errors_accordion.scss'; + import { ERROR_TABLE_ID_HEADER, ERROR_TABLE_ERROR_HEADER, @@ -60,14 +63,19 @@ export const SchemaErrorsAccordion: React.FC = ({ - - + + + + - {schema[fieldName]} + {schema[fieldName]} - {ERROR_TABLE_REVIEW_CONTROL} + {/* href is needed here because a button cannot be nested in a button or console will error and EuiAccordion uses a button to wrap this. */} + + {ERROR_TABLE_REVIEW_CONTROL} + ); @@ -76,12 +84,12 @@ export const SchemaErrorsAccordion: React.FC = ({ - + {ERROR_TABLE_ID_HEADER} {ERROR_TABLE_ERROR_HEADER} @@ -93,34 +101,21 @@ export const SchemaErrorsAccordion: React.FC = ({ const documentPath = getRoute && itemId ? getRoute(itemId, error.external_id) : ''; const viewButton = showViewButton && ( - - - - {ERROR_TABLE_VIEW_LINK} - - + + {ERROR_TABLE_VIEW_LINK} ); return ( - - -
- -
-
- - {error.error} + + + + {error.error} {showViewButton ? viewButton : } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx index 29cb2b7589220..7f7b26e380c55 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx @@ -10,8 +10,6 @@ import { useParams } from 'react-router-dom'; import { useActions, useValues } from 'kea'; -import { EuiSpacer } from '@elastic/eui'; - import { SchemaErrorsAccordion } from '../../../../../shared/schema/schema_errors_accordion'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; @@ -32,16 +30,13 @@ export const SchemaChangeErrors: React.FC = () => { }, []); return ( -
+ <> - -
- -
-
+ + ); };