diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index 3f4732f15f334..650ef94e1d3da 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -11,8 +11,11 @@ kibanaPipeline(timeoutMinutes: 240) { 'CODE_COVERAGE=1', // Enables coverage. Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc. ]) { workers.base(name: 'coverage-worker', size: 'l', ramDisk: false, bootstrapped: false) { - kibanaCoverage.runTests() - handleIngestion(TIME_STAMP) + catchError { + kibanaCoverage.runTests() + handleIngestion(TIME_STAMP) + } + handleFail() } } kibanaPipeline.sendMail() @@ -29,4 +32,13 @@ def handleIngestion(timestamp) { kibanaCoverage.uploadCoverageStaticSite(timestamp) } +def handleFail() { + def buildStatus = buildUtils.getBuildStatus() + if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { + slackNotifications.sendFailedBuild( + channel: '#kibana-qa', + username: 'Kibana QA' + ) + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4716346277029..48d70910f9bf1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -37,6 +37,7 @@ /examples/url_generators_examples/ @elastic/kibana-app-arch /examples/url_generators_explorer/ @elastic/kibana-app-arch /packages/kbn-interpreter/ @elastic/kibana-app-arch +/packages/elastic-datemath/ @elastic/kibana-app-arch /src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch /src/legacy/core_plugins/interpreter/ @elastic/kibana-app-arch /src/legacy/core_plugins/kibana_react/ @elastic/kibana-app-arch diff --git a/Jenkinsfile b/Jenkinsfile index 11dca544f3226..b6a36c79f877d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,6 +41,7 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true) { 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), + 'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'), 'xpack-securitySolutionCypress': { processNumber -> whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) diff --git a/docs/images/add-data-fv.png b/docs/images/add-data-fv.png new file mode 100755 index 0000000000000..45313d133822c Binary files /dev/null and b/docs/images/add-data-fv.png differ diff --git a/docs/images/add-data-tutorials.png b/docs/images/add-data-tutorials.png new file mode 100644 index 0000000000000..74deedc57b42e Binary files /dev/null and b/docs/images/add-data-tutorials.png differ diff --git a/docs/management/managing-remote-clusters.asciidoc b/docs/management/managing-remote-clusters.asciidoc index 00ec5c7d2ddea..51d9f42a0b83e 100644 --- a/docs/management/managing-remote-clusters.asciidoc +++ b/docs/management/managing-remote-clusters.asciidoc @@ -31,6 +31,10 @@ to reproduce indices in the remote cluster on a local cluster. [role="screenshot"] image::images/add_remote_cluster.png[][UI for adding a remote cluster] +To create an index pattern to search across clusters, +use the same syntax that you’d use in a raw cross-cluster search request in {es}: :. +See <> for examples. + [float] [[manage-remote-clusters]] === Manage remote clusters diff --git a/docs/maps/images/fu_gs_select_source_file_upload.png b/docs/maps/images/fu_gs_select_source_file_upload.png index 6939f6a82b297..4fe1162acb29c 100644 Binary files a/docs/maps/images/fu_gs_select_source_file_upload.png and b/docs/maps/images/fu_gs_select_source_file_upload.png differ diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 00acb73bd276f..6137e028db3fd 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -1,44 +1,105 @@ [[connect-to-elasticsearch]] -== Connect Kibana with Elasticsearch +== Adding data -Before you can start using Kibana, you need to tell it which Elasticsearch indices you want to explore. -The first time you access Kibana, you are prompted to define an _index pattern_ that matches the name of -one or more of your indices. That's it. That's all you need to configure to start using Kibana. You can -add index patterns at any time from the <>. +To start working with your data in {kib}, you can: -TIP: By default, Kibana connects to the Elasticsearch instance running on `localhost`. To connect to a -different Elasticsearch instance, modify the Elasticsearch URL in the `kibana.yml` configuration file and -restart Kibana. For information about using Kibana with your production nodes, see <>. +* Upload a CSV, JSON, or log file with the File Data Visualizer. -To configure the Elasticsearch indices you want to access with Kibana: +* Upload geospatial data with the GeoJSON Upload feature. -. Point your browser at port 5601 to access the Kibana UI. For example, `localhost:5601` or -`http://YOURDOMAIN.com:5601`. -+ -image:images/Start-Page.png[Kibana start page] -+ -. Specify an index pattern that matches the name of one or more of your Elasticsearch indices. The pattern -can include an asterisk (*) to matches zero or more characters in an index's name. When filling out your -index pattern, any matched indices will be displayed. -. Click *Next Step* to select the index field that contains the timestamp you want to use to perform time-based -comparisons. Kibana reads the index mapping to list all of the fields that contain a timestamp. If your -index doesn't have time-based data, choose *I don't want to use the Time Filter* option. -+ -. Click *Create index pattern* to add the index pattern. This first pattern is automatically configured as the default. -When you have more than one index pattern, you can designate which one to use as the default by clicking -on the star icon above the index pattern title from *Management > Index Patterns*. +* Index logs, metrics, events, or application data by setting up a Beats module. + +* Connect {kib} with existing {es} indices. + +If you're not ready to use your own data, you can add a <> +to see all that you can do in {kib}. + +[float] +[[upload-data-kibana]] +=== Upload a CSV, JSON, or log file + +To visualize data in a CSV, JSON, or log file, you can +upload it using the File Data Visualizer. On the home page, +click *Import a CSV, NDSON, or log file*, and then drag your file into the +File Data Visualizer. + +You can upload a file up to 100 MB. This value is configurable up to 1 GB in +<>. + +[role="screenshot"] +image::images/add-data-fv.png[File Data Visualizer] + +The File Data Visualizer uses the {ref}/ml-find-file-structure.html[find_file_structure API] to analyze +the uploaded file and to suggest ingest pipelines and mappings for your data. + +NOTE: This feature is not intended for use as part of a +repeated production process, but rather for the initial exploration of your data. + +[float] +[[upload-geoipdata-kibana]] +=== Upload geospatial data + +To visualize geospatial data in a point or shape file, you can upload it using the <> +feature in *Elastic Maps*, and then use that data as a layer in a map. +The data is also available for use in the broader Kibana ecosystem, for example, +in visualizations and Canvas workpads. +With GeoJSON Upload, you can upload a file up to 50 MB. + +[role="screenshot"] +image::images/fu_gs_select_source_file_upload.png[] -All done! Kibana is now connected to your Elasticsearch data. Kibana displays a read-only list of fields -configured for the matching index. [float] -[[explore]] -=== Start Exploring your Data! -You're ready to dive in to your data: +[[add-data-tutorial-kibana]] +=== Index metrics, log, security, and application data -* Search and browse your data interactively from the <> page. -* Chart and map your data from the <> page. -* Create and view custom dashboards from the <> page. +The built-in data tutorials can help you quickly get up and running with +metrics data, log analytics, security events, and application data. +These tutorials walk you through installing and configuring a +Beats data shipper to periodically collect and send data to {es}. +You can then use the pre-built dashboards to explore and analyze the data. -For a step-by-step introduction to these core Kibana concepts, see the <> tutorial. +You access the tutorials from the home page. +If a tutorial doesn’t exist for your data, go to the {beats-ref}/beats-reference.html[Beats overview] +to learn about other data shippers in the Beats family. + +[role="screenshot"] +image::images/add-data-tutorials.png[Add Data tutorials] + + +[float] +[[connect-to-es]] +=== Connect with {es} indices + +To visualize data in existing {es} indices, you must +create an index pattern that matches the names of the indices that you want to explore. +When you add data with the File Data Visualizer, GeoJSON Upload feature, +or built-in tutorial, an index pattern is created for you. + +. Go to *Stack Management*, and then click *Index Patterns*. + +. Click *Create index pattern*. + +. Specify an index pattern that matches the name of one or more of your Elasticsearch indices. ++ +For example, an index pattern can point to your Apache data from yesterday, +`filebeat-apache-4-3-2022`, or any index that matches the pattern, `filebeat-*`. +Using a wildcard is the more popular approach. + + +. Click *Next Step*, and then select the index field that contains the timestamp you want to use to perform time-based +comparisons. ++ +Kibana reads the index mapping and lists all fields that contain a timestamp. If your +index doesn't have time-based data, choose *I don't want to use the Time Filter*. ++ +You must select a time field to use global time filters on your dashboards. + +. Click *Create index pattern*. ++ +{kib} is now configured to access your {es} indices. +You’ll see a list of fields configured for the matching index. +You can designate your index pattern as the default by clicking the star icon on this page. ++ +When searching in *Discover* and creating visualizations, you choose a pattern +from the index pattern menu to specify the {es} indices that contain the data you want to explore. diff --git a/package.json b/package.json index 88ef07ba17cac..0873ab8c158e1 100644 --- a/package.json +++ b/package.json @@ -429,7 +429,7 @@ "eslint-plugin-prefer-object-spread": "^1.2.1", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.17.0", - "eslint-plugin-react-hooks": "^2.3.0", + "eslint-plugin-react-hooks": "^4.0.4", "eslint-plugin-react-perf": "^3.2.3", "exit-hook": "^2.2.0", "faker": "1.1.0", diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 0ab0048619358..8e2fd1c9182ff 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -14,6 +14,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@types/parse-link-header": "^1.0.0", + "@types/puppeteer": "^3.0.0", "@types/strip-ansi": "^5.2.1", "@types/xml2js": "^0.4.5", "diff": "^4.0.1" @@ -25,6 +26,7 @@ "getopts": "^2.2.4", "glob": "^7.1.2", "parse-link-header": "^1.0.1", + "puppeteer": "^3.3.0", "strip-ansi": "^5.2.0", "rxjs": "^6.5.3", "tar-fs": "^1.16.3", diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 585ce8181df5f..0bc7cc664df68 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -58,3 +58,5 @@ export { runFailedTestsReporterCli } from './failed_tests_reporter'; export { makeJunitReportPath } from './junit_report_path'; export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; + +export * from './page_load_metrics'; diff --git a/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts b/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts new file mode 100644 index 0000000000000..013d49a29a51c --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ToolingLog } from '@kbn/dev-utils'; +import { NavigationOptions, createUrl, navigateToApps } from './navigation'; + +export async function capturePageLoadMetrics(log: ToolingLog, options: NavigationOptions) { + const responsesByPageView = await navigateToApps(log, options); + + const assetSizeMeasurements = new Map(); + + const numberOfPagesVisited = responsesByPageView.size; + + for (const [, frameResponses] of responsesByPageView) { + for (const [, { url, dataLength }] of frameResponses) { + if (url.length === 0) { + throw new Error('navigateToApps(); failed to identify the url of the request'); + } + if (assetSizeMeasurements.has(url)) { + assetSizeMeasurements.set(url, [dataLength].concat(assetSizeMeasurements.get(url) || [])); + } else { + assetSizeMeasurements.set(url, [dataLength]); + } + } + } + + return Array.from(assetSizeMeasurements.entries()) + .map(([url, measurements]) => { + const baseUrl = createUrl('/', options.appConfig.url); + const relativeUrl = url + // remove the baseUrl (expect the trailing slash) to make url relative + .replace(baseUrl.slice(0, -1), '') + // strip the build number from asset urls + .replace(/^\/\d+\//, '/'); + return [relativeUrl, measurements] as const; + }) + .filter(([url, measurements]) => { + if (measurements.length !== numberOfPagesVisited) { + // ignore urls seen only on some pages + return false; + } + + if (url.startsWith('data:')) { + // ignore data urls since they are already counted by other assets + return false; + } + + if (url.startsWith('/api/') || url.startsWith('/internal/')) { + // ignore api requests since they don't have deterministic sizes + return false; + } + + const allMetricsAreEqual = measurements.every((x, i) => + i === 0 ? true : x === measurements[i - 1] + ); + if (!allMetricsAreEqual) { + throw new Error(`measurements for url [${url}] are not equal [${measurements.join(',')}]`); + } + + return true; + }) + .map(([url, measurements]) => { + return { group: 'page load asset size', id: url, value: measurements[0] }; + }); +} diff --git a/packages/kbn-test/src/page_load_metrics/cli.ts b/packages/kbn-test/src/page_load_metrics/cli.ts new file mode 100644 index 0000000000000..95421384c79cb --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/cli.ts @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Url from 'url'; + +import { run, createFlagError } from '@kbn/dev-utils'; +import { resolve, basename } from 'path'; +import { capturePageLoadMetrics } from './capture_page_load_metrics'; + +const defaultScreenshotsDir = resolve(__dirname, 'screenshots'); + +export function runPageLoadMetricsCli() { + run( + async ({ flags, log }) => { + const kibanaUrl = flags['kibana-url']; + if (!kibanaUrl || typeof kibanaUrl !== 'string') { + throw createFlagError('Expect --kibana-url to be a string'); + } + + const parsedUrl = Url.parse(kibanaUrl); + + const [username, password] = parsedUrl.auth + ? parsedUrl.auth.split(':') + : [flags.username, flags.password]; + + if (typeof username !== 'string' || typeof password !== 'string') { + throw createFlagError( + 'Mising username and/or password, either specify in --kibana-url or pass --username and --password' + ); + } + + const headless = !flags.head; + + const screenshotsDir = flags.screenshotsDir || defaultScreenshotsDir; + + if (typeof screenshotsDir !== 'string' || screenshotsDir === basename(screenshotsDir)) { + throw createFlagError('Expect screenshotsDir to be valid path string'); + } + + const metrics = await capturePageLoadMetrics(log, { + headless, + appConfig: { + url: kibanaUrl, + username, + password, + }, + screenshotsDir, + }); + for (const metric of metrics) { + log.info(`${metric.id}: ${metric.value}`); + } + }, + { + description: `Loads several pages with Puppeteer to capture the size of assets`, + flags: { + string: ['kibana-url', 'username', 'password', 'screenshotsDir'], + boolean: ['head'], + default: { + username: 'elastic', + password: 'changeme', + debug: true, + screenshotsDir: defaultScreenshotsDir, + }, + help: ` + --kibana-url Url for Kibana we should connect to, can include login info + --head Run puppeteer with graphical user interface + --username Set username, defaults to 'elastic' + --password Set password, defaults to 'changeme' + --screenshotsDir Set screenshots directory, defaults to '${defaultScreenshotsDir}' + `, + }, + } + ); +} diff --git a/packages/kbn-test/src/page_load_metrics/event.ts b/packages/kbn-test/src/page_load_metrics/event.ts new file mode 100644 index 0000000000000..481954bbf672e --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/event.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface ResponseReceivedEvent { + frameId: string; + loaderId: string; + requestId: string; + response: Record; + timestamp: number; + type: string; +} + +export interface DataReceivedEvent { + encodedDataLength: number; + dataLength: number; + requestId: string; + timestamp: number; +} diff --git a/packages/kbn-test/src/page_load_metrics/index.ts b/packages/kbn-test/src/page_load_metrics/index.ts new file mode 100644 index 0000000000000..4309d558518a6 --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './cli'; +export { capturePageLoadMetrics } from './capture_page_load_metrics'; diff --git a/packages/kbn-test/src/page_load_metrics/navigation.ts b/packages/kbn-test/src/page_load_metrics/navigation.ts new file mode 100644 index 0000000000000..21dc681951b21 --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/navigation.ts @@ -0,0 +1,165 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Fs from 'fs'; +import Url from 'url'; +import _ from 'lodash'; +import puppeteer from 'puppeteer'; +import { resolve } from 'path'; +import { ToolingLog } from '@kbn/dev-utils'; +import { ResponseReceivedEvent, DataReceivedEvent } from './event'; + +export interface NavigationOptions { + headless: boolean; + appConfig: { url: string; username: string; password: string }; + screenshotsDir: string; +} + +export type NavigationResults = Map>; + +interface FrameResponse { + url: string; + dataLength: number; +} + +function joinPath(pathA: string, pathB: string) { + return `${pathA.endsWith('/') ? pathA.slice(0, -1) : pathA}/${ + pathB.startsWith('/') ? pathB.slice(1) : pathB + }`; +} + +export function createUrl(path: string, url: string) { + const baseUrl = Url.parse(url); + return Url.format({ + protocol: baseUrl.protocol, + hostname: baseUrl.hostname, + port: baseUrl.port, + pathname: joinPath(baseUrl.pathname || '', path), + }); +} + +async function loginToKibana( + log: ToolingLog, + browser: puppeteer.Browser, + options: NavigationOptions +) { + log.debug(`log in to the app..`); + const page = await browser.newPage(); + const loginUrl = createUrl('/login', options.appConfig.url); + await page.goto(loginUrl, { + waitUntil: 'networkidle0', + }); + await page.type('[data-test-subj="loginUsername"]', options.appConfig.username); + await page.type('[data-test-subj="loginPassword"]', options.appConfig.password); + await page.click('[data-test-subj="loginSubmit"]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); + await page.close(); +} + +export async function navigateToApps(log: ToolingLog, options: NavigationOptions) { + const browser = await puppeteer.launch({ headless: options.headless, args: ['--no-sandbox'] }); + const devToolsResponses: NavigationResults = new Map(); + const apps = [ + { path: '/app/discover', locator: '[data-test-subj="discover-sidebar"]' }, + { path: '/app/home', locator: '[data-test-subj="homeApp"]' }, + { path: '/app/canvas', locator: '[data-test-subj="create-workpad-button"]' }, + { path: '/app/maps', locator: '[title="Maps"]' }, + { path: '/app/apm', locator: '[data-test-subj="apmMainContainer"]' }, + ]; + + await loginToKibana(log, browser, options); + + await Promise.all( + apps.map(async (app) => { + const page = await browser.newPage(); + page.setCacheEnabled(false); + page.setDefaultNavigationTimeout(0); + const frameResponses = new Map(); + devToolsResponses.set(app.path, frameResponses); + + const client = await page.target().createCDPSession(); + await client.send('Network.enable'); + + function getRequestData(requestId: string) { + if (!frameResponses.has(requestId)) { + frameResponses.set(requestId, { url: '', dataLength: 0 }); + } + + return frameResponses.get(requestId)!; + } + + client.on('Network.responseReceived', (event: ResponseReceivedEvent) => { + getRequestData(event.requestId).url = event.response.url; + }); + + client.on('Network.dataReceived', (event: DataReceivedEvent) => { + getRequestData(event.requestId).dataLength += event.dataLength; + }); + + const url = createUrl(app.path, options.appConfig.url); + log.debug(`goto ${url}`); + await page.goto(url, { + waitUntil: 'networkidle0', + }); + + let readyAttempt = 0; + let selectorFound = false; + while (!selectorFound) { + readyAttempt += 1; + try { + await page.waitForSelector(app.locator, { timeout: 5000 }); + selectorFound = true; + } catch (error) { + log.error( + `Page '${app.path}' was not loaded properly, unable to find '${ + app.locator + }', url: ${page.url()}` + ); + + if (readyAttempt < 6) { + continue; + } + + const failureDir = resolve(options.screenshotsDir, 'failure'); + const screenshotPath = resolve( + failureDir, + `${app.path.slice(1).split('/').join('_')}_navigation.png` + ); + Fs.mkdirSync(failureDir, { recursive: true }); + + await page.bringToFront(); + await page.screenshot({ + path: screenshotPath, + type: 'png', + fullPage: true, + }); + log.debug(`Saving screenshot to ${screenshotPath}`); + + throw new Error(`Page load timeout: ${app.path} not loaded after 30 seconds`); + } + } + + await page.close(); + }) + ); + + await browser.close(); + + return devToolsResponses; +} diff --git a/scripts/page_load_metrics.js b/scripts/page_load_metrics.js new file mode 100644 index 0000000000000..37500c26e0b20 --- /dev/null +++ b/scripts/page_load_metrics.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env'); +require('@kbn/test').runPageLoadMetricsCli(); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 7afb607192cae..f0db3a25e313d 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -17,7 +17,7 @@ * under the License. */ -import { map } from 'rxjs/operators'; +import { map, shareReplay } from 'rxjs/operators'; import { combineLatest } from 'rxjs'; import { CoreContext } from '../core_context'; import { PluginWrapper } from './plugin'; @@ -107,8 +107,8 @@ export function createPluginInitializerContext( * @param ConfigClass A class (not an instance of a class) that contains a * static `schema` that we validate the config at the given `path` against. */ - create() { - return coreContext.configService.atPath(pluginManifest.configPath); + create() { + return coreContext.configService.atPath(pluginManifest.configPath).pipe(shareReplay(1)); }, createIfExists() { return coreContext.configService.optionalAtPath(pluginManifest.configPath); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index 4f69d45c192e9..69b57a498936e 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -74,6 +74,7 @@ export class KibanaMigrator { private readonly status$ = new BehaviorSubject({ status: 'waiting', }); + private readonly activeMappings: IndexMapping; /** * Creates an instance of KibanaMigrator. @@ -100,6 +101,9 @@ export class KibanaMigrator { validateDoc: docValidator(savedObjectValidations || {}), log: this.log, }); + // Building the active mappings (and associated md5sums) is an expensive + // operation so we cache the result + this.activeMappings = buildActiveMappings(this.mappingProperties); } /** @@ -172,7 +176,7 @@ export class KibanaMigrator { * */ public getActiveMappings(): IndexMapping { - return buildActiveMappings(this.mappingProperties); + return this.activeMappings; } /** diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index e23f8dec5927c..b093fe779cab7 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -136,7 +136,7 @@ export class SavedObjectsRepository { injectedConstructor: any = SavedObjectsRepository ): ISavedObjectsRepository { const mappings = migrator.getActiveMappings(); - const allTypes = Object.keys(getRootPropertiesObjects(mappings)); + const allTypes = typeRegistry.getAllTypes().map((t) => t.name); const serializer = new SavedObjectsSerializer(typeRegistry); const visibleTypes = allTypes.filter((type) => !typeRegistry.isHidden(type)); diff --git a/src/dev/code_coverage/nyc_config/nyc.functional.config.js b/src/dev/code_coverage/nyc_config/nyc.functional.config.js new file mode 100644 index 0000000000000..20d266ab9e2c3 --- /dev/null +++ b/src/dev/code_coverage/nyc_config/nyc.functional.config.js @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const defaultExclude = require('@istanbuljs/schema/default-exclude'); +const extraExclude = ['data/optimize/**', 'src/core/server/**', '**/test/**']; +const path = require('path'); + +module.exports = { + 'temp-dir': process.env.COVERAGE_TEMP_DIR + ? path.resolve(process.env.COVERAGE_TEMP_DIR, 'functional') + : 'target/kibana-coverage/functional', + 'report-dir': 'target/kibana-coverage/functional-combined', + reporter: ['html', 'json-summary'], + exclude: extraExclude.concat(defaultExclude), +}; diff --git a/src/dev/code_coverage/nyc_config/nyc.jest.config.js b/src/dev/code_coverage/nyc_config/nyc.jest.config.js new file mode 100644 index 0000000000000..1f73347837ab3 --- /dev/null +++ b/src/dev/code_coverage/nyc_config/nyc.jest.config.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const path = require('path'); + +module.exports = { + 'temp-dir': process.env.COVERAGE_TEMP_DIR + ? path.resolve(process.env.COVERAGE_TEMP_DIR, 'jest') + : 'target/kibana-coverage/jest', + 'report-dir': 'target/kibana-coverage/jest-combined', + reporter: ['html', 'json-summary'], +}; diff --git a/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh b/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh index ff9cb36c894f8..707c6de3f88a0 100644 --- a/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh +++ b/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh @@ -1,10 +1,9 @@ #!/bin/bash -EXTRACT_START_DIR=tmp/extracted_coverage -EXTRACT_END_DIR=target/kibana-coverage -COMBINED_EXTRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR} +COVERAGE_TEMP_DIR=/tmp/extracted_coverage/target/kibana-coverage/ +export COVERAGE_TEMP_DIR echo "### Merge coverage reports" for x in jest functional; do - yarn nyc report --temp-dir $COMBINED_EXTRACT_DIR/${x} --report-dir $EXTRACT_END_DIR/${x}-combined --reporter=html --reporter=json-summary + yarn nyc report --nycrc-path src/dev/code_coverage/nyc_config/nyc.${x}.config.js done diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js index 7d84c27bd1ef0..63839b9d0f1d7 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.js @@ -27,14 +27,13 @@ import { importSavedObjectsFromStream, resolveSavedObjectsImportErrors, } from '../../../core/server/saved_objects'; -import { getRootPropertiesObjects } from '../../../core/server/saved_objects/mappings'; import { convertTypesToLegacySchema } from '../../../core/server/saved_objects/utils'; export function savedObjectsMixin(kbnServer, server) { const migrator = kbnServer.newPlatform.__internals.kibanaMigrator; const typeRegistry = kbnServer.newPlatform.start.core.savedObjects.getTypeRegistry(); const mappings = migrator.getActiveMappings(); - const allTypes = Object.keys(getRootPropertiesObjects(mappings)); + const allTypes = typeRegistry.getAllTypes().map((t) => t.name); const schema = new SavedObjectsSchema(convertTypesToLegacySchema(typeRegistry.getAllTypes())); const visibleTypes = allTypes.filter((type) => !schema.isHiddenType(type)); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 9c2ab0819776b..d98770842a0f0 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -254,6 +254,9 @@ export const npSetup = { }, share: { register: () => {}, + urlGenerators: { + registerUrlGenerator: () => {}, + }, }, devTools: { register: () => {}, diff --git a/src/plugins/charts/public/services/theme/theme.ts b/src/plugins/charts/public/services/theme/theme.ts index 166e1c539688a..e1e71573caa3a 100644 --- a/src/plugins/charts/public/services/theme/theme.ts +++ b/src/plugins/charts/public/services/theme/theme.ts @@ -42,8 +42,10 @@ export class ThemeService { /** A React hook for consuming the charts theme */ public useChartsTheme = () => { + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const [value, update] = useState(this.chartsDefaultTheme); + /* eslint-disable-next-line react-hooks/rules-of-hooks */ useEffect(() => { const s = this.chartsTheme$.subscribe(update); return () => s.unsubscribe(); diff --git a/src/plugins/console/public/application/containers/editor/editor.tsx b/src/plugins/console/public/application/containers/editor/editor.tsx index 0bfe837f2cd90..66d3cbab20ac5 100644 --- a/src/plugins/console/public/application/containers/editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/editor.tsx @@ -47,6 +47,7 @@ export const Editor = memo(({ loading }: Props) => { INITIAL_PANEL_WIDTH, ]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const onPanelWidthChange = useCallback( debounce((widths: number[]) => { storage.set(StorageKeys.WIDTH, widths); diff --git a/src/plugins/console/public/application/hooks/use_save_current_text_object.ts b/src/plugins/console/public/application/hooks/use_save_current_text_object.ts index ab517ba1bfdd1..1bd1a7fb09bd1 100644 --- a/src/plugins/console/public/application/hooks/use_save_current_text_object.ts +++ b/src/plugins/console/public/application/hooks/use_save_current_text_object.ts @@ -32,6 +32,7 @@ export const useSaveCurrentTextObject = () => { const { currentTextObject } = useEditorReadContext(); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ return useCallback( throttle( (text: string) => { diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts index 62a39ee898d3a..1b060c186db97 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts @@ -108,16 +108,32 @@ interface IplacementDirection { fits: boolean; } +/** + * Compare grid data by an ending y coordinate. Grid data with a smaller ending y coordinate + * comes first. + * @param a + * @param b + */ +function comparePanels(a: GridData, b: GridData): number { + if (a.y + a.h < b.y + b.h) { + return -1; + } + if (a.y + a.h > b.y + b.h) { + return 1; + } + // a.y === b.y + if (a.x + a.w <= b.x + b.w) { + return -1; + } + return 1; +} + export function placePanelBeside({ width, height, currentPanels, placeBesideId, }: IPanelPlacementBesideArgs): Omit { - // const clonedPanels = _.cloneDeep(currentPanels); - if (!placeBesideId) { - throw new Error('Place beside method called without placeBesideId'); - } const panelToPlaceBeside = currentPanels[placeBesideId]; if (!panelToPlaceBeside) { throw new PanelNotFoundError(); @@ -130,10 +146,11 @@ export function placePanelBeside({ const possiblePlacementDirections: IplacementDirection[] = [ { grid: { x: beside.x + beside.w, y: beside.y, w: width, h: height }, fits: true }, // right - { grid: { x: beside.x - width, y: beside.y, w: width, h: height }, fits: true }, // left + { grid: { x: 0, y: beside.y + beside.h, w: width, h: height }, fits: true }, // left side of next row { grid: { x: beside.x, y: beside.y + beside.h, w: width, h: height }, fits: true }, // bottom ]; + // first, we check if there is place around the current panel for (const direction of possiblePlacementDirections) { if ( direction.grid.x >= 0 && @@ -156,13 +173,32 @@ export function placePanelBeside({ } } // if we get here that means there is no blank space around the panel we are placing beside. This means it's time to mess up the dashboard's groove. Fun! - const [, , bottomPlacement] = possiblePlacementDirections; - for (const currentPanelGrid of otherPanels) { - if (bottomPlacement.grid.y <= currentPanelGrid.y) { - const movedPanel = _.cloneDeep(currentPanels[currentPanelGrid.i]); - movedPanel.gridData.y = movedPanel.gridData.y + bottomPlacement.grid.h; - currentPanels[currentPanelGrid.i] = movedPanel; + /** + * 1. sort the panels in the grid + * 2. place the cloned panel to the bottom + * 3. reposition the panels after the cloned panel in the grid + */ + const grid = otherPanels.sort(comparePanels); + + let position = 0; + for (position; position < grid.length; position++) { + if (beside.i === grid[position].i) { + break; } } + const bottomPlacement = possiblePlacementDirections[2]; + // place to the bottom and move all other panels + let originalPositionInTheGrid = grid[position + 1].i; + const diff = + bottomPlacement.grid.y + + bottomPlacement.grid.h - + currentPanels[originalPositionInTheGrid].gridData.y; + + for (let j = position + 1; j < grid.length; j++) { + originalPositionInTheGrid = grid[j].i; + const movedPanel = _.cloneDeep(currentPanels[originalPositionInTheGrid]); + movedPanel.gridData.y = movedPanel.gridData.y + diff; + currentPanels[originalPositionInTheGrid] = movedPanel; + } return bottomPlacement.grid; } diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 18ed632e0a8ec..81e84e3198072 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -135,12 +135,14 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) queryRef.current = props.query; setQuery(props.query || defaultQuery); } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [defaultQuery, props.query]); useEffect(() => { if (props.onQuerySubmit !== onQuerySubmitRef.current) { onQuerySubmitRef.current = props.onQuerySubmit; } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [props.onQuerySubmit]); // handle service state updates. diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 0b3a07e98624e..14dd399697b56 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -1,6 +1,7 @@ { "id": "discover", "version": "kibana", + "optionalPlugins": ["share"], "server": true, "ui": true, "requiredPlugins": [ diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 359d91325f064..4154fdfeb3ff4 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -27,3 +27,4 @@ export function plugin(initializerContext: PluginInitializerContext) { export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './saved_searches'; export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable'; +export { DISCOVER_APP_URL_GENERATOR } from './url_generator'; diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index c394fe2c11a71..e4314426bfce5 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -34,6 +34,9 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { savedSearchLoader: {} as any, + urlGenerator: { + createUrl: jest.fn(), + } as any, }; return startContract; }; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 4323e3d8deda4..091288e3e65aa 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -34,7 +34,7 @@ import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { ChartsPluginStart } from 'src/plugins/charts/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; -import { SharePluginStart } from 'src/plugins/share/public'; +import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public'; import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public'; import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; @@ -43,7 +43,7 @@ import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../d import { SavedObjectLoader } from '../../saved_objects/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; - +import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; import { DocViewTable } from './application/components/table/table'; @@ -59,6 +59,17 @@ import { import { createSavedSearchesLoader } from './saved_searches'; import { registerFeature } from './register_feature'; import { buildServices } from './build_services'; +import { + DiscoverUrlGeneratorState, + DISCOVER_APP_URL_GENERATOR, + DiscoverUrlGenerator, +} from './url_generator'; + +declare module '../../share/public' { + export interface UrlGeneratorStateMapping { + [DISCOVER_APP_URL_GENERATOR]: UrlGeneratorState; + } +} /** * @public @@ -76,12 +87,31 @@ export interface DiscoverSetup { export interface DiscoverStart { savedSearchLoader: SavedObjectLoader; + + /** + * `share` plugin URL generator for Discover app. Use it to generate links into + * Discover application, example: + * + * ```ts + * const url = await plugins.discover.urlGenerator.createUrl({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + */ + readonly urlGenerator: undefined | UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>; } /** * @internal */ export interface DiscoverSetupPlugins { + share?: SharePluginSetup; uiActions: UiActionsSetup; embeddable: EmbeddableSetup; kibanaLegacy: KibanaLegacySetup; @@ -122,6 +152,7 @@ export class DiscoverPlugin private stopUrlTracking: (() => void) | undefined = undefined; private servicesInitialized: boolean = false; private innerAngularInitialized: boolean = false; + private urlGenerator?: DiscoverStart['urlGenerator']; /** * why are those functions public? they are needed for some mocha tests @@ -131,6 +162,17 @@ export class DiscoverPlugin public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>; setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { + const baseUrl = core.http.basePath.prepend('/app/discover'); + + if (plugins.share) { + this.urlGenerator = plugins.share.urlGenerators.registerUrlGenerator( + new DiscoverUrlGenerator({ + appBasePath: baseUrl, + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); + } + this.docViewsRegistry = new DocViewsRegistry(); setDocViewsRegistry(this.docViewsRegistry); this.docViewsRegistry.addDocView({ @@ -158,7 +200,7 @@ export class DiscoverPlugin // so history is lazily created (when app is mounted) // this prevents redundant `#` when not in discover app getHistory: getScopedHistory, - baseUrl: core.http.basePath.prepend('/app/discover'), + baseUrl, defaultSubUrl: '#/', storageKey: `lastUrl:${core.http.basePath.get()}:discover`, navLinkUpdater$: this.appStateUpdater, @@ -266,6 +308,7 @@ export class DiscoverPlugin }; return { + urlGenerator: this.urlGenerator, savedSearchLoader: createSavedSearchesLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns: plugins.data.indexPatterns, diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts new file mode 100644 index 0000000000000..cf9beb246fea2 --- /dev/null +++ b/src/plugins/discover/public/url_generator.test.ts @@ -0,0 +1,259 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DiscoverUrlGenerator } from './url_generator'; +import { hashedItemStore, getStatesFromKbnUrl } from '../../kibana_utils/public'; +// eslint-disable-next-line +import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; +import { FilterStateStore } from '../../data/common'; + +const appBasePath: string = 'xyz/app/discover'; +const indexPatternId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; +const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d'; + +interface SetupParams { + useHash?: boolean; +} + +const setup = async ({ useHash = false }: SetupParams = {}) => { + const generator = new DiscoverUrlGenerator({ + appBasePath, + useHash, + }); + + return { + generator, + }; +}; + +beforeEach(() => { + // @ts-ignore + hashedItemStore.storage = mockStorage; +}); + +describe('Discover url generator', () => { + test('can create a link to Discover with no state and no saved search', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({}); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(url.startsWith(appBasePath)).toBe(true); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('can create a link to a saved search in Discover', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ savedSearchId }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(url.startsWith(`${appBasePath}#/${savedSearchId}`)).toBe(true); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('can specify specific index pattern', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + indexPatternId, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + index: indexPatternId, + }); + expect(_g).toEqual({}); + }); + + test('can specify specific time range', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + time: { + from: 'now-15m', + mode: 'relative', + to: 'now', + }, + }); + }); + + test('can specify query', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + query: { + language: 'kuery', + query: 'foo', + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + query: { + language: 'kuery', + query: 'foo', + }, + }); + expect(_g).toEqual({}); + }); + + test('can specify local and global filters', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + + test('can set refresh interval', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + refreshInterval: { + pause: false, + value: 666, + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + refreshInterval: { + pause: false, + value: 666, + }, + }); + }); + + test('can set time range', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + timeRange: { + from: 'now-3h', + to: 'now', + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + time: { + from: 'now-3h', + to: 'now', + }, + }); + }); + + describe('useHash property', () => { + describe('when default useHash is set to false', () => { + test('when using default, sets index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(true); + }); + + test('when enabling useHash, does not set index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + useHash: true, + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(false); + }); + }); + + describe('when default useHash is set to true', () => { + test('when using default, does not set index pattern ID in the generated URL', async () => { + const { generator } = await setup({ useHash: true }); + const url = await generator.createUrl({ + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(false); + }); + + test('when disabling useHash, sets index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + useHash: false, + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(true); + }); + }); + }); +}); diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts new file mode 100644 index 0000000000000..42d689050d5ad --- /dev/null +++ b/src/plugins/discover/public/url_generator.ts @@ -0,0 +1,114 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + TimeRange, + Filter, + Query, + esFilters, + QueryState, + RefreshInterval, +} from '../../data/public'; +import { setStateToKbnUrl } from '../../kibana_utils/public'; +import { UrlGeneratorsDefinition } from '../../share/public'; + +export const DISCOVER_APP_URL_GENERATOR = 'DISCOVER_APP_URL_GENERATOR'; + +export interface DiscoverUrlGeneratorState { + /** + * Optionally set saved search ID. + */ + savedSearchId?: string; + + /** + * Optionally set index pattern ID. + */ + indexPatternId?: string; + + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval; + + /** + * Optionally apply filers. + */ + filters?: Filter[]; + + /** + * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the + * saved dashboard has a query saved with it, this will _replace_ that query. + */ + query?: Query; + + /** + * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines + * whether to hash the data in the url to avoid url length issues. + */ + useHash?: boolean; +} + +interface Params { + appBasePath: string; + useHash: boolean; +} + +export class DiscoverUrlGenerator + implements UrlGeneratorsDefinition { + constructor(private readonly params: Params) {} + + public readonly id = DISCOVER_APP_URL_GENERATOR; + + public readonly createUrl = async ({ + filters, + indexPatternId, + query, + refreshInterval, + savedSearchId, + timeRange, + useHash = this.params.useHash, + }: DiscoverUrlGeneratorState): Promise => { + const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : ''; + const appState: { + query?: Query; + filters?: Filter[]; + index?: string; + } = {}; + const queryState: QueryState = {}; + + if (query) appState.query = query; + if (filters) appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); + if (indexPatternId) appState.index = indexPatternId; + + if (timeRange) queryState.time = timeRange; + if (filters) queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let url = `${this.params.appBasePath}#/${savedSearchPath}`; + url = setStateToKbnUrl('_g', queryState, { useHash }, url); + url = setStateToKbnUrl('_a', appState, { useHash }, url); + + return url; + }; +} diff --git a/src/plugins/kibana_utils/public/core/create_start_service_getter.ts b/src/plugins/kibana_utils/public/core/create_start_service_getter.ts index 14e2588a0a9cf..5e4a0f2bc7aeb 100644 --- a/src/plugins/kibana_utils/public/core/create_start_service_getter.ts +++ b/src/plugins/kibana_utils/public/core/create_start_service_getter.ts @@ -19,16 +19,17 @@ import { CoreStart, StartServicesAccessor } from '../../../../core/public'; -export interface StartServices { +export interface StartServices { plugins: Plugins; self: OwnContract; - core: CoreStart; + core: Core; } -export type StartServicesGetter = () => StartServices< - Plugins, - OwnContract ->; +export type StartServicesGetter< + Plugins = unknown, + OwnContract = unknown, + Core = CoreStart +> = () => StartServices; /** * Use this utility to create a synchronous *start* service getter in *setup* diff --git a/src/plugins/share/public/url_generators/url_generator_service.ts b/src/plugins/share/public/url_generators/url_generator_service.ts index 13c1b94acdd07..b63e2a45d6812 100644 --- a/src/plugins/share/public/url_generators/url_generator_service.ts +++ b/src/plugins/share/public/url_generators/url_generator_service.ts @@ -24,7 +24,7 @@ import { UrlGeneratorInternal } from './url_generator_internal'; import { UrlGeneratorContract } from './url_generator_contract'; export interface UrlGeneratorsStart { - getUrlGenerator: (urlGeneratorId: UrlGeneratorId) => UrlGeneratorContract; + getUrlGenerator: (urlGeneratorId: T) => UrlGeneratorContract; } export interface UrlGeneratorsSetup { diff --git a/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx index a316a087c8bcb..ae3da8e203a57 100644 --- a/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx @@ -38,6 +38,7 @@ function HasExtendedBoundsParamEditor(props: AggParamEditorProps) { setValue(value && agg.params.min_doc_count); } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [agg.params.min_doc_count, setValue, value]); return ( diff --git a/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts b/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts index ee24e2b42113d..950c856349230 100644 --- a/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts +++ b/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts @@ -33,6 +33,7 @@ const CUSTOM_METRIC = { }; function useCompatibleAggCallback(aggFilter: AggFilter) { + /* eslint-disable-next-line react-hooks/exhaustive-deps */ return useCallback(isCompatibleAggregation(aggFilter), [aggFilter]); } diff --git a/src/plugins/vis_type_timelion/public/components/panel.tsx b/src/plugins/vis_type_timelion/public/components/panel.tsx index 4c28e4e5a18ab..99c5532c04832 100644 --- a/src/plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/plugins/vis_type_timelion/public/components/panel.tsx @@ -102,6 +102,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { [chartElem] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const highlightSeries = useCallback( debounce(({ currentTarget }: JQuery.TriggeredEvent) => { const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); @@ -295,6 +296,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { [plot, legendValueNumbers, unhighlightSeries, legendCaption] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedSetLegendNumbers = useCallback( debounce(setLegendNumbers, DEBOUNCE_DELAY, { maxWait: DEBOUNCE_DELAY, diff --git a/test/functional/apps/bundles/index.js b/test/functional/apps/bundles/index.js index 503517a98c69e..ead6412564751 100644 --- a/test/functional/apps/bundles/index.js +++ b/test/functional/apps/bundles/index.js @@ -25,7 +25,7 @@ export default function ({ getService }) { const supertest = getService('supertest'); describe('bundle compression', function () { - this.tags('ciGroup12'); + this.tags(['ciGroup12', 'skipCoverage']); let buildNum; before(async () => { diff --git a/test/functional/apps/context/_filters.js b/test/functional/apps/context/_filters.js index 470ef462b9d9d..66888d441954e 100644 --- a/test/functional/apps/context/_filters.js +++ b/test/functional/apps/context/_filters.js @@ -17,8 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; - const TEST_INDEX_PATTERN = 'logstash-*'; const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI'; const TEST_ANCHOR_FILTER_FIELD = 'geo.src'; @@ -40,20 +38,19 @@ export default function ({ getService, getPageObjects }) { }); it('inclusive filter should be addable via expanded doc table rows', async function () { - await docTable.toggleRowExpanded({ isAnchorRow: true }); - - await retry.try(async () => { + await retry.waitFor(`filter ${TEST_ANCHOR_FILTER_FIELD} in filterbar`, async () => { + await docTable.toggleRowExpanded({ isAnchorRow: true }); const anchorDetailsRow = await docTable.getAnchorDetailsRow(); await docTable.addInclusiveFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - expect( - await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true) - ).to.be(true); + + return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true); + }); + await retry.waitFor(`filter matching docs in docTable`, async () => { const fields = await docTable.getFields(); - const hasOnlyFilteredRows = fields + return fields .map((row) => row[2]) .every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE); - expect(hasOnlyFilteredRows).to.be(true); }); }); @@ -64,26 +61,27 @@ export default function ({ getService, getPageObjects }) { await filterBar.toggleFilterEnabled(TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - await retry.try(async () => { - expect( - await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false) - ).to.be(true); + await retry.waitFor(`a disabled filter in filterbar`, async () => { + return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false); + }); + + await retry.waitFor('filters are disabled', async () => { const fields = await docTable.getFields(); const hasOnlyFilteredRows = fields .map((row) => row[2]) .every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE); - expect(hasOnlyFilteredRows).to.be(false); + return hasOnlyFilteredRows === false; }); }); it('filter for presence should be addable via expanded doc table rows', async function () { await docTable.toggleRowExpanded({ isAnchorRow: true }); - await retry.try(async () => { + await retry.waitFor('an exists filter in the filterbar', async () => { const anchorDetailsRow = await docTable.getAnchorDetailsRow(); await docTable.addExistsFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - expect(await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true)).to.be(true); + return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true); }); }); }); diff --git a/test/functional/apps/context/_size.js b/test/functional/apps/context/_size.js index 3beb070b50deb..067a23daacb4a 100644 --- a/test/functional/apps/context/_size.js +++ b/test/functional/apps/context/_size.js @@ -16,69 +16,69 @@ * specific language governing permissions and limitations * under the License. */ - -import expect from '@kbn/expect'; - const TEST_INDEX_PATTERN = 'logstash-*'; const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI'; -const TEST_DEFAULT_CONTEXT_SIZE = 7; -const TEST_STEP_SIZE = 3; +const TEST_DEFAULT_CONTEXT_SIZE = 2; +const TEST_STEP_SIZE = 2; export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); const docTable = getService('docTable'); const PageObjects = getPageObjects(['context']); + let expectedRowLength = 2 * TEST_DEFAULT_CONTEXT_SIZE + 1; - // FLAKY: https://github.com/elastic/kibana/issues/53888 - describe.skip('context size', function contextSize() { + describe('context size', function contextSize() { before(async function () { await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, 'context:step': `${TEST_STEP_SIZE}`, }); + await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); }); it('should default to the `context:defaultSize` setting', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); - - await retry.try(async function () { - expect(await docTable.getRowsText()).to.have.length(2 * TEST_DEFAULT_CONTEXT_SIZE + 1); - }); - await retry.try(async function () { - const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker(); - expect(await predecessorCountPicker.getAttribute('value')).to.equal( - `${TEST_DEFAULT_CONTEXT_SIZE}` - ); - }); - await retry.try(async function () { - const successorCountPicker = await PageObjects.context.getSuccessorCountPicker(); - expect(await successorCountPicker.getAttribute('value')).to.equal( - `${TEST_DEFAULT_CONTEXT_SIZE}` - ); - }); + await retry.waitFor( + `number of rows displayed initially is ${expectedRowLength}`, + async function () { + const rows = await docTable.getRowsText(); + return rows.length === expectedRowLength; + } + ); + await retry.waitFor( + `predecessor count picker is set to ${TEST_DEFAULT_CONTEXT_SIZE}`, + async function () { + const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker(); + const value = await predecessorCountPicker.getAttribute('value'); + return value === String(TEST_DEFAULT_CONTEXT_SIZE); + } + ); }); it('should increase according to the `context:step` setting when clicking the `load newer` button', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); await PageObjects.context.clickPredecessorLoadMoreButton(); + expectedRowLength += TEST_STEP_SIZE; - await retry.try(async function () { - expect(await docTable.getRowsText()).to.have.length( - 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 - ); - }); + await retry.waitFor( + `number of rows displayed after clicking load more predecessors is ${expectedRowLength}`, + async function () { + const rows = await docTable.getRowsText(); + return rows.length === expectedRowLength; + } + ); }); it('should increase according to the `context:step` setting when clicking the `load older` button', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); await PageObjects.context.clickSuccessorLoadMoreButton(); + expectedRowLength += TEST_STEP_SIZE; - await retry.try(async function () { - expect(await docTable.getRowsText()).to.have.length( - 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 - ); - }); + await retry.waitFor( + `number of rows displayed after clicking load more successors is ${expectedRowLength}`, + async function () { + const rows = await docTable.getRowsText(); + return rows.length === expectedRowLength; + } + ); }); }); } diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 91e9c020a0e7c..fe5694efc35da 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -67,17 +67,17 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo * @param appUrl Kibana URL */ private async loginIfPrompted(appUrl: string, insertTimestamp: boolean) { + // Disable the welcome screen. This is relevant for environments + // which don't allow to use the yml setting, e.g. cloud production. + // It is done here so it applies to logins but also to a login re-use. + await browser.setLocalStorageItem('home:welcome:show', 'false'); + let currentUrl = await browser.getCurrentUrl(); log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`); await testSubjects.find('kibanaChrome', 6 * defaultFindTimeout); // 60 sec waiting const loginPage = currentUrl.includes('/login'); const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout'); - // Disable the welcome screen. This is relevant for environments - // which don't allow to use the yml setting, e.g. cloud production. - // It is done here so it applies to logins but also to a login re-use. - await browser.setLocalStorageItem('home:welcome:show', 'false'); - if (loginPage && !wantedLoginPage) { log.debug('Found login page'); if (config.get('security.disableTestUser')) { diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 778142d95e4b4..60d7f0406f4c9 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -31,4 +31,12 @@ else mkdir -p ../kibana/target/kibana-coverage/functional mv target/kibana-coverage/functional/* ../kibana/target/kibana-coverage/functional/ fi + + echo " -> moving junit output, silently fail in case of no report" + mkdir -p ../kibana/target/junit + mv target/junit/* ../kibana/target/junit/ || echo "copying junit failed" + + echo " -> copying screenshots and html for failures" + cp -r test/functional/screenshots/* ../kibana/test/functional/screenshots/ || echo "copying screenshots failed" + cp -r test/functional/failure_debug ../kibana/test/functional/ || echo "copying html failed" fi diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index a6e600630364e..648605135b359 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -32,4 +32,12 @@ else mkdir -p ../../kibana/target/kibana-coverage/functional mv ../target/kibana-coverage/functional/* ../../kibana/target/kibana-coverage/functional/ fi + + echo " -> moving junit output, silently fail in case of no report" + mkdir -p ../../kibana/target/junit + mv ../target/junit/* ../../kibana/target/junit/ || echo "copying junit failed" + + echo " -> copying screenshots and html for failures" + cp -r test/functional/screenshots/* ../../kibana/x-pack/test/functional/screenshots/ || echo "copying screenshots failed" + cp -r test/functional/failure_debug ../../kibana/x-pack/test/functional/ || echo "copying html failed" fi \ No newline at end of file diff --git a/test/scripts/jenkins_xpack_page_load_metrics.sh b/test/scripts/jenkins_xpack_page_load_metrics.sh new file mode 100644 index 0000000000000..679f0b8d2ddc5 --- /dev/null +++ b/test/scripts/jenkins_xpack_page_load_metrics.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +source test/scripts/jenkins_test_setup_xpack.sh + +checks-reporter-with-killswitch "Capture Kibana page load metrics" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$installDir" \ + --config test/page_load_metrics/config.ts; diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy index 0305f86475a9a..66b16566418b5 100644 --- a/vars/kibanaCoverage.groovy +++ b/vars/kibanaCoverage.groovy @@ -98,7 +98,7 @@ def collectVcsInfo(title) { def generateReports(title) { kibanaPipeline.bash(""" - source src/dev/ci_setup/setup_env.sh + source src/dev/ci_setup/setup_env.sh true # bootstrap from x-pack folder cd x-pack yarn kbn bootstrap --prefer-offline diff --git a/x-pack/.gitignore b/x-pack/.gitignore index 305032926579b..68262c4bf734b 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -3,6 +3,7 @@ /target /test/functional/failure_debug /test/functional/screenshots +/test/page_load_metrics/screenshots /test/functional/apps/reporting/reports/session /test/reporting/configs/failure_debug/ /legacy/plugins/reporting/.chromium/ diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index d17e5d1a74a30..85b40d33c4089 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -48,7 +48,8 @@ "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": ["plugins/uptime"], - "xpack.watcher": "plugins/watcher" + "xpack.watcher": "plugins/watcher", + "xpack.observability": "plugins/observability" }, "translations": [ "plugins/translations/translations/zh-CN.json", diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 01b966ebe359b..74553bbde0cd6 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -43,6 +43,7 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector '!**/scripts/**', '!**/mocks/**', '!**/plugins/apm/e2e/**', + '!**/plugins/siem/cypress/**', ], coveragePathIgnorePatterns: ['.*\\.d\\.ts'], coverageDirectory: `${kibanaDirectory}/target/kibana-coverage/jest`, diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json index 7f3ed80ed62fa..a1cd895bb3cd6 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json @@ -5,6 +5,6 @@ "configPath": ["ui_actions_enhanced_examples"], "server": false, "ui": true, - "requiredPlugins": ["uiActionsEnhanced", "data"], + "requiredPlugins": ["uiActionsEnhanced", "data", "discover"], "optionalPlugins": [] } diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx index 0237e128c5a2f..da9b0e921fb1c 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx @@ -31,9 +31,7 @@ export const DiscoverDrilldownConfig: React.FC = ( onIndexPatternSelect, customIndexPattern, onCustomIndexPatternToggle, - carryFiltersAndQuery, onCarryFiltersAndQueryToggle, - carryTimeRange, onCarryTimeRangeToggle, }) => { return ( @@ -82,9 +80,10 @@ export const DiscoverDrilldownConfig: React.FC = ( {!!onCarryFiltersAndQueryToggle && ( @@ -92,9 +91,10 @@ export const DiscoverDrilldownConfig: React.FC = ( {!!onCarryTimeRangeToggle && ( diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx index 91e05d16b4362..ba88f49861ffe 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx @@ -22,7 +22,7 @@ const isOutputWithIndexPatterns = ( }; export interface Params { - start: StartServicesGetter>; + start: StartServicesGetter>; } export class DashboardToDiscoverDrilldown implements Drilldown { @@ -54,6 +54,10 @@ export class DashboardToDiscoverDrilldown implements Drilldown => { + const { urlGenerator } = this.params.start().plugins.discover; + + if (!urlGenerator) throw new Error('Discover URL generator not available.'); + let indexPatternId = !!config.customIndexPattern && !!config.indexPatternId ? config.indexPatternId : ''; @@ -64,8 +68,9 @@ export class DashboardToDiscoverDrilldown implements Drilldown => { diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts index 3f28854351f55..8034c378cc64f 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts @@ -14,14 +14,17 @@ import { DashboardHelloWorldDrilldown } from './dashboard_hello_world_drilldown' import { DashboardToUrlDrilldown } from './dashboard_to_url_drilldown'; import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown'; import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; +import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public'; export interface SetupDependencies { data: DataPublicPluginSetup; + discover: DiscoverSetup; uiActionsEnhanced: AdvancedUiActionsSetup; } export interface StartDependencies { data: DataPublicPluginStart; + discover: DiscoverStart; uiActionsEnhanced: AdvancedUiActionsStart; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts index 459e9d2b03f92..992b2cb16fb06 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts @@ -21,6 +21,7 @@ import { ExecutorSubActionGetIncidentParamsSchema, ExecutorSubActionHandshakeParamsSchema, } from './schema'; +import { LicenseType } from '../../../../../legacy/common/constants'; export interface AnyParams { [index: string]: string | number | object | undefined | null; @@ -51,6 +52,7 @@ export type Comment = TypeOf; export interface ExternalServiceConfiguration { id: string; name: string; + minimumLicenseRequired: LicenseType; } export interface ExternalServiceCredentials { diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts index 315d13b5aa773..dd8d971b7df44 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts @@ -120,9 +120,7 @@ export const createConnector = ({ configurationUtilities, executor = createConnectorExecutor({ api, createExternalService }), }: CreateActionTypeArgs): ActionType => ({ - id: config.id, - name: config.name, - minimumLicenseRequired: 'platinum', + ...config, validate: { config: schema.object(validationSchema.config, { validate: curry(validate.config)(configurationUtilities), diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts index 7e415109f1bd9..54f28e447010a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts @@ -10,4 +10,5 @@ import * as i18n from './translations'; export const config: ExternalServiceConfiguration = { id: '.jira', name: i18n.NAME, + minimumLicenseRequired: 'gold', }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts index 4ad8108c3b137..70d53ab79f631 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts @@ -10,4 +10,5 @@ import * as i18n from './translations'; export const config: ExternalServiceConfiguration = { id: '.servicenow', name: i18n.NAME, + minimumLicenseRequired: 'platinum', }; diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index cb8600ed2c214..56c427e67ad4c 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; import styled from 'styled-components'; +import { EuiThemeProvider } from '../../../observability/public'; import { CoreStart, AppMountParameters } from '../../../../../src/core/public'; import { ApmPluginSetupDeps } from '../plugin'; import { ApmPluginContext } from '../context/ApmPluginContext'; @@ -18,7 +19,10 @@ import { LocationProvider } from '../context/LocationContext'; import { MatchedRouteProvider } from '../context/MatchedRouteContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; import { AlertsContextProvider } from '../../../triggers_actions_ui/public'; -import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { + KibanaContextProvider, + useUiSetting$, +} from '../../../../../src/plugins/kibana_react/public'; import { px, unit, units } from '../style/variables'; import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; import { APMIndicesPermission } from '../components/app/APMIndicesPermission'; @@ -35,18 +39,22 @@ const MainContainer = styled.div` `; const App = () => { + const [darkMode] = useUiSetting$('theme:darkMode'); + return ( - - - - - - {routes.map((route, i) => ( - - ))} - - - + + + + + + + {routes.map((route, i) => ( + + ))} + + + + ); }; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 5bb678d1c08af..50eb85715969a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -79,6 +79,7 @@ export function AgentConfigurationCreateEdit({ ..._newConfig, settings: existingConfig?.settings || {}, })); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [existingConfig]); // update newConfig when existingConfig has loaded diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index d39ad530c1b4c..1244dd01a3b43 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -109,10 +109,12 @@ export const TransactionDistribution: FunctionComponent = ( bucketIndex, } = props; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYShort = useCallback(getFormatYShort(transactionType), [ transactionType, ]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYLong = useCallback(getFormatYLong(transactionType), [ transactionType, ]); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 988edb197a230..2507eca9ff663 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -66,6 +66,7 @@ export const TransactionActionMenu: FunctionComponent = ({ { key: 'transaction.name', value: transaction?.transaction.name }, { key: 'transaction.type', value: transaction?.transaction.type }, ].filter((filter): filter is Filter => typeof filter.value === 'string'), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [transaction] ); diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx index 28b836cd2c650..2db4659c83603 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx @@ -106,6 +106,7 @@ describe('useFetcher', () => { jest.useFakeTimers(); const hook = renderHook( + /* eslint-disable-next-line react-hooks/exhaustive-deps */ ({ callback, args }) => useFetcher(callback, args), { initialProps: { @@ -165,6 +166,7 @@ describe('useFetcher', () => { it('should return the same object reference when data is unchanged between rerenders', async () => { const hook = renderHook( + /* eslint-disable-next-line react-hooks/exhaustive-deps */ ({ callback, args }) => useFetcher(callback, args), { initialProps: { diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 76320efe617ea..0939c51b16605 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -75,7 +75,7 @@ export class ApmPlugin implements Plugin { core.application.register({ id: 'apm', title: 'APM', - order: 8100, + order: 8300, euiIconType: 'apmApp', appRoute: '/app/apm', icon: 'plugins/apm/public/icon.svg', diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index 0144e573fc146..ada86adf84cfd 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -51,7 +51,7 @@ describe('doesIlmPolicyExist', () => { await clusterClientAdapter.doesIlmPolicyExist('foo'); expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', { method: 'GET', - path: '_ilm/policy/foo', + path: '/_ilm/policy/foo', }); }); @@ -78,7 +78,7 @@ describe('createIlmPolicy', () => { await clusterClientAdapter.createIlmPolicy('foo', { args: true }); expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', { method: 'PUT', - path: '_ilm/policy/foo', + path: '/_ilm/policy/foo', body: { args: true }, }); }); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 7fd239ca49369..a036bfb74e408 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -41,7 +41,7 @@ export class ClusterClientAdapter { public async doesIlmPolicyExist(policyName: string): Promise { const request = { method: 'GET', - path: `_ilm/policy/${policyName}`, + path: `/_ilm/policy/${policyName}`, }; try { await this.callEs('transport.request', request); @@ -55,7 +55,7 @@ export class ClusterClientAdapter { public async createIlmPolicy(policyName: string, policy: unknown): Promise { const request = { method: 'PUT', - path: `_ilm/policy/${policyName}`, + path: `/_ilm/policy/${policyName}`, body: policy, }; try { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx index 52033a00327c0..d26575f65dfec 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx @@ -41,6 +41,7 @@ export const MetricsAlertDropdown = () => { , ]; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [kibana.services]); return ( diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index db5cfb1416dec..7a71bb68bc54f 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -90,6 +90,7 @@ export const Expressions: React.FC = (props) => { aggregation: 'avg', }; } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [alertsContext.metadata]); const updateParams = useCallback( @@ -109,6 +110,7 @@ export const Expressions: React.FC = (props) => { timeUnit: timeUnit ?? defaultExpression.timeUnit, }); setAlertParams('criteria', exp); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [setAlertParams, alertParams.criteria, timeSize, timeUnit]); const removeExpression = useCallback( @@ -119,6 +121,7 @@ export const Expressions: React.FC = (props) => { setAlertParams('criteria', exp); } }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [setAlertParams, alertParams.criteria] ); @@ -133,6 +136,7 @@ export const Expressions: React.FC = (props) => { [setAlertParams, derivedIndexPattern] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ onFilterChange, ]); @@ -162,6 +166,7 @@ export const Expressions: React.FC = (props) => { setTimeSize(ts || undefined); setAlertParams('criteria', criteria); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams.criteria, setAlertParams] ); @@ -175,6 +180,7 @@ export const Expressions: React.FC = (props) => { setTimeUnit(tu as TimeUnit); setAlertParams('criteria', criteria); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams.criteria, setAlertParams] ); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 64a5792689d52..a886dccf105cf 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -90,6 +90,7 @@ export const ExpressionChart: React.FC = ({ : (value: number) => `${value}`; }, [data]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]); if (loading || !data) { diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx index c48b5b9a2cc58..47a0f037816bc 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx @@ -41,6 +41,7 @@ export const InventoryAlertDropdown = () => { , ]; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [kibana.services]); return ( diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx index f4fab113cdd17..074464fb55414 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx @@ -117,6 +117,7 @@ export const Expressions: React.FC = (props) => { timeUnit: timeUnit ?? defaultExpression.timeUnit, }); setAlertParams('criteria', exp); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [setAlertParams, alertParams.criteria, timeSize, timeUnit]); const removeExpression = useCallback( @@ -141,6 +142,7 @@ export const Expressions: React.FC = (props) => { [derivedIndexPattern, setAlertParams] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ onFilterChange, ]); diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx index d81d11e01d4a5..609f99805fe9c 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx @@ -137,6 +137,7 @@ export const Editor: React.FC = (props) => { } else { return []; } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [sourceStatus]); const updateCount = useCallback( @@ -176,6 +177,7 @@ export const Editor: React.FC = (props) => { ? [...alertParams.criteria, DEFAULT_CRITERIA] : [DEFAULT_CRITERIA]; setAlertParams('criteria', nextCriteria); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [alertParams, setAlertParams]); const removeCriterion = useCallback( @@ -185,6 +187,7 @@ export const Editor: React.FC = (props) => { }); setAlertParams('criteria', nextCriteria); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams, setAlertParams] ); diff --git a/x-pack/plugins/infra/public/components/header/header.tsx b/x-pack/plugins/infra/public/components/header/header.tsx index fa71426f83645..47ee1857da591 100644 --- a/x-pack/plugins/infra/public/components/header/header.tsx +++ b/x-pack/plugins/infra/public/components/header/header.tsx @@ -31,10 +31,12 @@ export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) const setBreadcrumbs = useCallback(() => { return chrome?.setBreadcrumbs(breadcrumbs || []); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [breadcrumbs, chrome]); const setBadge = useCallback(() => { return chrome?.setBadge(badge); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [badge, chrome]); useEffect(() => { diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index 5fe9a45a7ceed..d5b2a0aaa61c0 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -269,6 +269,7 @@ const useFetchEntriesEffect = ( } }; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const fetchNewerEntries = useCallback( throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), [props, state.bottomCursor] @@ -330,10 +331,12 @@ const useFetchEntriesEffect = ( props.timestampsLastUpdate, ]; + /* eslint-disable react-hooks/exhaustive-deps */ useEffect(fetchNewEntriesEffect, fetchNewEntriesEffectDependencies); useEffect(fetchMoreEntriesEffect, fetchMoreEntriesEffectDependencies); useEffect(streamEntriesEffect, streamEntriesEffectDependencies); useEffect(expandRangeEffect, expandRangeEffectDependencies); + /* eslint-enable react-hooks/exhaustive-deps */ return { fetchNewerEntries, checkForNewEntries: runFetchNewEntriesRequest }; }; diff --git a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts index 7c903f59002dc..d5a43c0d6cffa 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts @@ -82,6 +82,7 @@ export const useLogFilterState: (props: { } return true; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [filterQueryDraft]); const serializedFilterQuery = useMemo(() => (filterQuery ? filterQuery.serializedQuery : null), [ diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index 670988d680147..80aab6237518f 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -78,6 +78,7 @@ export const useLogSource = ({ [sourceId, fetch] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const logIndicesExist = useMemo(() => (sourceStatus?.logIndexNames?.length ?? 0) > 0, [ sourceStatus, ]); @@ -87,6 +88,7 @@ export const useLogSource = ({ fields: sourceStatus?.logIndexFields ?? [], title: sourceConfiguration?.configuration.name ?? 'unknown', }), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [sourceConfiguration, sourceStatus] ); diff --git a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts index 94e2537a67a2a..54d565d9ee223 100644 --- a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts +++ b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts @@ -76,6 +76,7 @@ export const useSourceViaHttp = ({ title: pickIndexPattern(response?.source, indexType), }; }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [response, type] ); diff --git a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx index 2a70edc9b9a57..cfa9a711f7743 100644 --- a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx @@ -35,6 +35,7 @@ export const useBulkGetSavedObject = (type: string) => { }; fetchData(); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx index 8313d496a0651..0efb862ad2eb4 100644 --- a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx @@ -40,6 +40,7 @@ export const useCreateSavedObject = (type: string) => { }; save(); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx index 3f2d15b3b86aa..e353a79b19073 100644 --- a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx @@ -29,6 +29,7 @@ export const useDeleteSavedObject = (type: string) => { }; dobj(); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx index 8b0ab45f6e6d1..8eb6db6103ed8 100644 --- a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx @@ -37,6 +37,7 @@ export const useFindSavedObject = { title: loadDataErrorTitle, }); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [services.notifications] ); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 156c9a919440e..3c8db3f8246c0 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -127,6 +127,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { [setAutoRefresh] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const hasResults = useMemo(() => (logEntryRate?.histogramBuckets?.length ?? 0) > 0, [ logEntryRate, ]); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 11ea137c95a1f..a1d3d56beee2c 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -29,6 +29,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ const logEntryRateSeries = useMemo( () => results?.histogramBuckets ? getLogEntryRateSeriesForPartition(results, partitionId) : [], + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [results, partitionId] ); const anomalyAnnotations = useMemo( @@ -41,6 +42,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ major: [], critical: [], }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [results, partitionId] ); const totalNumberOfLogEntries = useMemo( @@ -48,6 +50,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ results?.histogramBuckets ? getTotalNumberOfLogEntriesForPartition(results, partitionId) : undefined, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [results, partitionId] ); return ( diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx index 34c4202ab8b65..f41158e114c7d 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx @@ -42,6 +42,7 @@ export const LogsSettingsPage = () => { const availableFields = useMemo( () => sourceStatus?.logIndexFields.map((field) => field.name) ?? [], + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [sourceStatus] ); diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index b6e6710a0b3b4..cf3eae263ed59 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -28,6 +28,7 @@ const MODAL_MARGIN = 25; export const PageViewLogInContext: React.FC = () => { const { sourceConfiguration } = useLogSourceContext(); const { textScale, textWrap } = useContext(LogViewConfiguration.Context); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const columnConfigurations = useMemo(() => sourceConfiguration?.configuration.logColumns ?? [], [ sourceConfiguration, ]); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 8b5b191ccfdd9..1452772e49ca1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -76,6 +76,7 @@ export const Layout = () => { const intervalAsString = convertIntervalToString(interval); const dataBounds = calculateBoundsFromNodes(nodes); const bounds = autoBounds ? dataBounds : boundsOverride; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]); return ( diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx index 6a4d6521855a9..dee2e0c9f457f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx @@ -45,6 +45,7 @@ export const ChartSectionVis = ({ }: VisSectionProps) => { const isDarkMode = useUiSetting('theme:darkMode'); const [dateFormat] = useKibanaUiSetting('dateFormat'); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ formatter, formatterTemplate, diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx index 4c75003616117..88e7c0c08e441 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx @@ -23,6 +23,7 @@ export const SubSection: FunctionComponent = ({ isLiveStreaming, stopLiveStreaming, }) => { + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const metric = useMemo(() => metrics?.find((m) => m.id === id), [id, metrics]); if (!children || !metric) { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx index d079b7bb93d73..2a218c1c78aa3 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx @@ -87,6 +87,7 @@ export const MetricsExplorerChart = ({ [dateFormat] ), }; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]); const dataDomain = calculateDomain(series, metrics, chartOptions.stack); const domain = diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 652beef9d94cd..b3765db43335a 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -39,7 +39,7 @@ export class Plugin defaultMessage: 'Logs', }), euiIconType: 'logsApp', - order: 8000, + order: 8100, appRoute: '/app/logs', category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { @@ -66,7 +66,7 @@ export class Plugin defaultMessage: 'Metrics', }), euiIconType: 'metricsApp', - order: 8001, + order: 8200, appRoute: '/app/metrics', category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 1dd7e660deaa9..6fab78951038f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -169,6 +169,7 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { ))} ), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [agentConfig, configId, agentStatus] ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx index cdc4f1c63a11d..057970aa1ee92 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx @@ -48,6 +48,7 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre items: [ { icon: 'dashboardApp', + /* eslint-disable-next-line react-hooks/rules-of-hooks */ href: useKibanaLink(`/dashboard/${dashboards[0].id || ''}`), name: actionNameSingular, }, @@ -70,6 +71,7 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre items: dashboards.map((dashboard) => { return { icon: 'dashboardApp', + /* eslint-disable-next-line react-hooks/rules-of-hooks */ href: useKibanaLink(`/dashboard/${dashboard.id || ''}`), name: dashboard.title, }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx index 1a7681584ff15..5bb0464801f70 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx @@ -90,6 +90,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { ), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [agentData, agentId, getHref] ); @@ -141,6 +142,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { ))} ) : undefined, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [agentConfigData, agentData, getHref, isAgentConfigLoading] ); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts index a8fba834d65ab..f4ef9bdbe5b6d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts @@ -25,6 +25,21 @@ class MockField extends AbstractField { } } +export class MockMbMap { + _paintPropertyCalls: unknown[]; + + constructor() { + this._paintPropertyCalls = []; + } + setPaintProperty(...args: unknown[]) { + this._paintPropertyCalls.push([...args]); + } + + getPaintPropertyCalls(): unknown[] { + return this._paintPropertyCalls; + } +} + export const mockField: IField = new MockField({ fieldName: 'foobar', origin: FIELD_ORIGIN.SOURCE, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js index 898da439c44aa..a0af2fbb939d8 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js @@ -110,6 +110,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty { _getMbDataDrivenSize({ targetName, minSize, maxSize, minValue, maxValue }) { const lookup = this.supportsMbFeatureState() ? 'feature-state' : 'get'; + + const stops = + minValue === maxValue ? [maxValue, maxSize] : [minValue, minSize, maxValue, maxSize]; return [ 'interpolate', ['linear'], @@ -120,10 +123,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { fieldName: targetName, fallback: 0, }), - minValue, - minSize, - maxValue, - maxSize, + ...stops, ]; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx index 34f3e796f409f..c60547f3606c5 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IVectorStyle } from '../vector_style'; - jest.mock('ui/new_platform'); jest.mock('../components/vector_style_editor', () => ({ VectorStyleEditor: () => { @@ -18,68 +16,22 @@ import { shallow } from 'enzyme'; // @ts-ignore import { DynamicSizeProperty } from './dynamic_size_property'; -import { StyleMeta } from '../style_meta'; -import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../common/constants'; -import { DataRequest } from '../../../util/data_request'; -import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { VECTOR_STYLES } from '../../../../../common/constants'; import { IField } from '../../../fields/field'; +import { MockMbMap } from './__tests__/test_util'; -// @ts-ignore -const mockField: IField = { - async getLabel() { - return 'foobar_label'; - }, - getName() { - return 'foobar'; - }, - getRootName() { - return 'foobar'; - }, - getOrigin() { - return FIELD_ORIGIN.SOURCE; - }, - supportsFieldMeta() { - return true; - }, - canValueBeFormatted() { - return true; - }, - async getDataType() { - return 'number'; - }, -}; +import { mockField, MockLayer, MockStyle } from './__tests__/test_util'; -// @ts-ignore -const mockLayer: IVectorLayer = { - getDataRequest(): DataRequest | undefined { - return undefined; - }, - getStyle(): IVectorStyle { - // @ts-ignore - return { - getStyleMeta(): StyleMeta { - return new StyleMeta({ - geometryTypes: { - isPointsOnly: true, - isLinesOnly: false, - isPolygonsOnly: false, - }, - fieldMeta: { - foobar: { - range: { min: 0, max: 100, delta: 100 }, - categories: { categories: [] }, - }, - }, - }); - }, - }; - }, -}; - -const makeProperty: DynamicSizeProperty = (options: object) => { - return new DynamicSizeProperty(options, VECTOR_STYLES.ICON_SIZE, mockField, mockLayer, () => { - return (x: string) => x + '_format'; - }); +const makeProperty = (options: object, mockStyle: MockStyle, field: IField = mockField) => { + return new DynamicSizeProperty( + options, + VECTOR_STYLES.ICON_SIZE, + field, + new MockLayer(mockStyle), + () => { + return (x: string) => x + '_format'; + } + ); }; const defaultLegendParams = { @@ -89,7 +41,7 @@ const defaultLegendParams = { describe('renderLegendDetailRow', () => { test('Should render as range', async () => { - const sizeProp = makeProperty(); + const sizeProp = makeProperty({}, new MockStyle({ min: 0, max: 100 })); const legendRow = sizeProp.renderLegendDetailRow(defaultLegendParams); const component = shallow(legendRow); @@ -100,3 +52,70 @@ describe('renderLegendDetailRow', () => { expect(component).toMatchSnapshot(); }); }); + +describe('syncSize', () => { + test('Should sync with circle-radius prop', async () => { + const sizeProp = makeProperty({ minSize: 8, maxSize: 32 }, new MockStyle({ min: 0, max: 100 })); + const mockMbMap = new MockMbMap(); + + sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); + + expect(mockMbMap.getPaintPropertyCalls()).toEqual([ + [ + 'foobar', + 'circle-radius', + [ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['feature-state', 'foobar'], null], + -1, + ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 0], + ], + 0, + ], + 0, + 8, + 100, + 32, + ], + ], + ]); + }); + + test('Should truncate interpolate expression to max when no delta', async () => { + const sizeProp = makeProperty( + { minSize: 8, maxSize: 32 }, + new MockStyle({ min: 100, max: 100 }) + ); + const mockMbMap = new MockMbMap(); + + sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); + + expect(mockMbMap.getPaintPropertyCalls()).toEqual([ + [ + 'foobar', + 'circle-radius', + [ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['feature-state', 'foobar'], null], + 99, + ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 100], + ], + 0, + ], + 100, + 32, + ], + ], + ]); + }); +}); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 0154f92576c4a..58f8528236bb9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -73,6 +73,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = ); } + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const colorRange = useColorRange( COLOR_RANGE.BLUE, COLOR_RANGE_SCALE.INFLUENCER, diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx new file mode 100644 index 0000000000000..21a9fabf445f1 --- /dev/null +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; +import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; +import { Home } from '../pages/home'; +import { PluginContext } from '../context/plugin_context'; + +export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { + const i18nCore = core.i18n; + const isDarkMode = core.uiSettings.get('theme:darkMode'); + ReactDOM.render( + + + + + + + , + element + ); + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +}; diff --git a/x-pack/plugins/observability/public/assets/observability_overview.png b/x-pack/plugins/observability/public/assets/observability_overview.png new file mode 100644 index 0000000000000..70be08af9745a Binary files /dev/null and b/x-pack/plugins/observability/public/assets/observability_overview.png differ diff --git a/x-pack/plugins/observability/public/context/plugin_context.tsx b/x-pack/plugins/observability/public/context/plugin_context.tsx new file mode 100644 index 0000000000000..7d705e7a6cc05 --- /dev/null +++ b/x-pack/plugins/observability/public/context/plugin_context.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createContext } from 'react'; +import { AppMountContext } from 'kibana/public'; + +export interface PluginContextValue { + core: AppMountContext['core']; +} + +export const PluginContext = createContext({} as PluginContextValue); diff --git a/x-pack/plugins/observability/public/hooks/use_plugin_context.tsx b/x-pack/plugins/observability/public/hooks/use_plugin_context.tsx new file mode 100644 index 0000000000000..eeec115f0d286 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_plugin_context.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import { PluginContext } from '../context/plugin_context'; + +export function usePluginContext() { + return useContext(PluginContext); +} diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx new file mode 100644 index 0000000000000..072d3c47d3a55 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiCard, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiImage, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import styled from 'styled-components'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { appsSection, tryItOutItemsSection } from './section'; + +const Container = styled.div` + min-height: calc(100vh - 48px); + background: ${(props) => props.theme.eui.euiColorEmptyShade}; +`; + +const Title = styled.div` + background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; + border-bottom: ${(props) => props.theme.eui.euiBorderThin}; +`; + +const Page = styled.div` + width: 100%; + max-width: 1200px; + margin: 0 auto; + overflow: hidden; +} +`; + +const EuiCardWithoutPadding = styled(EuiCard)` + padding: 0; +`; + +export const Home = () => { + const { core } = usePluginContext(); + + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('xpack.observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, [core]); + + return ( + + + <Page> + <EuiSpacer size="xxl" /> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiIcon type="logoObservability" size="xxl" /> + </EuiFlexItem> + <EuiFlexItem> + <EuiTitle size="m"> + <h1> + {i18n.translate('xpack.observability.home.title', { + defaultMessage: 'Observability', + })} + </h1> + </EuiTitle> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xxl" /> + </Page> + + + + + {/* title and description */} + + +

+ {i18n.translate('xpack.observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', + })} +

+
+ + + {i18n.translate('xpack.observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
+ + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( + + } + title={ + +

{app.title}

+
+ } + description={app.description} + /> +
+ ))} +
+
+ + + +
+
+ + {/* Get started button */} + + + + + {i18n.translate('xpack.observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + + + + + + {/* Try it out */} + + + + +

+ {i18n.translate('xpack.observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
+
+
+
+ + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + } + title={ + +

{item.title}

+
+ } + description={item.description} + target={item.target} + href={item.href} + /> +
+ ))} +
+ +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts new file mode 100644 index 0000000000000..f8bbfbfa30548 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +interface ISection { + id: string; + title: string; + icon: string; + description: string; + href?: string; + target?: '_blank'; +} + +export const appsSection: ISection[] = [ + { + id: 'logs', + title: i18n.translate('xpack.observability.section.apps.logs.title', { + defaultMessage: 'Logs', + }), + icon: 'logoLogging', + description: i18n.translate('xpack.observability.section.apps.logs.description', { + defaultMessage: + 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + }), + }, + { + id: 'apm', + title: i18n.translate('xpack.observability.section.apps.apm.title', { + defaultMessage: 'APM', + }), + icon: 'logoAPM', + description: i18n.translate('xpack.observability.section.apps.apm.description', { + defaultMessage: + 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + }), + }, + { + id: 'metrics', + title: i18n.translate('xpack.observability.section.apps.metrics.title', { + defaultMessage: 'Metrics', + }), + icon: 'logoMetrics', + description: i18n.translate('xpack.observability.section.apps.metrics.description', { + defaultMessage: + 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + }), + }, + { + id: 'uptime', + title: i18n.translate('xpack.observability.section.apps.uptime.title', { + defaultMessage: 'Uptime', + }), + icon: 'logoUptime', + description: i18n.translate('xpack.observability.section.apps.uptime.description', { + defaultMessage: + 'React to availability issues across your apps and services before they affect users.', + }), + }, +]; + +export const tryItOutItemsSection: ISection[] = [ + { + id: 'demo', + title: i18n.translate('xpack.observability.section.tryItOut.demo.title', { + defaultMessage: 'Demo Playground', + }), + icon: 'play', + description: '', + href: 'https://demo.elastic.co/', + target: '_blank', + }, + { + id: 'sampleData', + title: i18n.translate('xpack.observability.section.tryItOut.sampleData.title', { + defaultMessage: 'Add sample data', + }), + icon: 'documents', + description: '', + href: '/app/home#/tutorial_directory/sampleData', + }, +]; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index a7eb1c50a0392..f2c88a7b1c056 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -3,13 +3,37 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Plugin as PluginClass, PluginInitializerContext } from 'kibana/public'; +import { + AppMountParameters, + CoreSetup, + DEFAULT_APP_CATEGORIES, + Plugin as PluginClass, + PluginInitializerContext, +} from '../../../../src/core/public'; export type ClientSetup = void; export type ClientStart = void; -export class Plugin implements PluginClass { +export class Plugin implements PluginClass { constructor(context: PluginInitializerContext) {} - start() {} - setup() {} + + public setup(core: CoreSetup) { + core.application.register({ + id: 'observability-overview', + title: 'Overview', + order: 8000, + appRoute: '/app/observability', + category: DEFAULT_APP_CATEGORIES.observability, + + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services + const [coreStart] = await core.getStartServices(); + + return renderApp(coreStart, params); + }, + }); + } + public start() {} } diff --git a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts index 36cd4f280ac4c..d0426a5e67e46 100644 --- a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts +++ b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts @@ -19,6 +19,7 @@ export const useSubmitCode = (http: HttpSetup) => { const [response, setResponse] = useState(undefined); const [inProgress, setInProgress] = useState(false); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const submit = useCallback( debounce( async (config: Payload) => { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 482794804685d..d04d1f2c91b97 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -31,6 +31,7 @@ export const DEFAULT_INTERVAL_PAUSE = true; export const DEFAULT_INTERVAL_TYPE = 'manual'; export const DEFAULT_INTERVAL_VALUE = 300000; // ms export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges'; +export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51'; /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const DEFAULT_INDEX_PATTERN = [ diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 669fc89ffe804..bb63cf633747b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -70,7 +70,7 @@ export interface CaseProps extends Props { export const CaseComponent = React.memo( ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => { const basePath = window.location.origin + useBasePath(); - const caseLink = `${basePath}/app/siem#/case/${caseId}`; + const caseLink = `${basePath}/app/security#/case/${caseId}`; const search = useGetUrlSearch(navTabs.case); const [initLoadingData, setInitLoadingData] = useState(true); const { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts index 9523e2485a610..b8219ad52f5b0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts @@ -143,7 +143,7 @@ export const CASE_REFRESH = i18n.translate('xpack.securitySolution.case.caseView export const EMAIL_SUBJECT = (caseTitle: string) => i18n.translate('xpack.securitySolution.case.caseView.emailSubject', { values: { caseTitle }, - defaultMessage: 'SIEM Case - {caseTitle}', + defaultMessage: 'Security Case - {caseTitle}', }); export const EMAIL_BODY = (caseUrl: string) => diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx index 43d5351a5dce3..67963c7487826 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx @@ -52,7 +52,7 @@ describe('FieldMappingRow', () => { test('it pass the corrects props to mapping row', () => { const rows = wrapper.find(FieldMappingRow); rows.forEach((row, index) => { - expect(row.prop('siemField')).toEqual(mapping[index].source); + expect(row.prop('securitySolutionField')).toEqual(mapping[index].source); expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target); }); @@ -68,7 +68,7 @@ describe('FieldMappingRow', () => { const rows = newWrapper.find(FieldMappingRow); rows.forEach((row, index) => { - expect(row.prop('siemField')).toEqual(defaultMapping[index].source); + expect(row.prop('securitySolutionField')).toEqual(defaultMapping[index].source); expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx index 3d515941fc2f3..415faa96eeedd 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx @@ -132,7 +132,7 @@ const FieldMappingComponent: React.FC = ({ key={`${item.source}`} id={`${item.source}`} disabled={disabled} - siemField={item.source} + securitySolutionField={item.source} thirdPartyOptions={getThirdPartyOptions(item.source, selectedConnector.fields)} actionTypeOptions={actionTypeOptions} onChangeActionType={onChangeActionType} diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx index 3787a43ff2d28..a2acd0e20b6ad 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx @@ -51,7 +51,7 @@ describe('FieldMappingRow', () => { const props: RowProps = { id: 'title', disabled: false, - siemField: 'title', + securitySolutionField: 'title', thirdPartyOptions, actionTypeOptions, onChangeActionType, diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx index 922ea7222efce..6e688b213f82c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx @@ -20,7 +20,7 @@ import { AllThirdPartyFields } from '../../../common/lib/connectors/types'; export interface RowProps { id: string; disabled: boolean; - siemField: CaseField; + securitySolutionField: CaseField; thirdPartyOptions: Array>; actionTypeOptions: Array>; onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void; @@ -32,7 +32,7 @@ export interface RowProps { const FieldMappingRowComponent: React.FC = ({ id, disabled, - siemField, + securitySolutionField, thirdPartyOptions, actionTypeOptions, onChangeActionType, @@ -40,13 +40,15 @@ const FieldMappingRowComponent: React.FC = ({ selectedActionType, selectedThirdParty, }) => { - const siemFieldCapitalized = useMemo(() => capitalize(siemField), [siemField]); + const securitySolutionFieldCapitalized = useMemo(() => capitalize(securitySolutionField), [ + securitySolutionField, + ]); return ( - {siemFieldCapitalized} + {securitySolutionFieldCapitalized} @@ -58,7 +60,7 @@ const FieldMappingRowComponent: React.FC = ({ disabled={disabled} options={thirdPartyOptions} valueOfSelected={selectedThirdParty} - onChange={onChangeThirdParty.bind(null, siemField)} + onChange={onChangeThirdParty.bind(null, securitySolutionField)} data-test-subj={`case-configure-third-party-select-${id}`} /> @@ -67,7 +69,7 @@ const FieldMappingRowComponent: React.FC = ({ disabled={disabled} options={actionTypeOptions} valueOfSelected={selectedActionType} - onChange={onChangeActionType.bind(null, siemField)} + onChange={onChangeActionType.bind(null, securitySolutionField)} data-test-subj={`case-configure-action-type-select-${id}`} /> diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts index c256c6dedb918..9ef6ce2f3d4a9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts @@ -17,7 +17,7 @@ export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate( 'xpack.securitySolution.case.configureCases.incidentManagementSystemDesc', { defaultMessage: - 'You may optionally connect SIEM cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', + 'You may optionally connect Security cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', } ); @@ -53,7 +53,7 @@ export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsDesc', { defaultMessage: - 'Define how you wish SIEM cases to be closed. Automated case closures require an established connection to an external incident management system.', + 'Define how you wish Security cases to be closed. Automated case closures require an established connection to an external incident management system.', } ); @@ -67,21 +67,22 @@ export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate( export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsManual', { - defaultMessage: 'Manually close SIEM cases', + defaultMessage: 'Manually close Security cases', } ); export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident', { - defaultMessage: 'Automatically close SIEM cases when pushing new incident to external system', + defaultMessage: + 'Automatically close Security cases when pushing new incident to external system', } ); export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident', { - defaultMessage: 'Automatically close SIEM cases when incident is closed in external system', + defaultMessage: 'Automatically close Security cases when incident is closed in external system', } ); @@ -96,14 +97,14 @@ export const FIELD_MAPPING_DESC = i18n.translate( 'xpack.securitySolution.case.configureCases.fieldMappingDesc', { defaultMessage: - 'Map SIEM case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.', + 'Map Security case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.', } ); export const FIELD_MAPPING_FIRST_COL = i18n.translate( 'xpack.securitySolution.case.configureCases.fieldMappingFirstCol', { - defaultMessage: 'SIEM case field', + defaultMessage: 'Security case field', } ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx index 8e07706d9c6ba..ae9f1ec7469e4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx @@ -18,7 +18,7 @@ const onSaveContent = jest.fn(); const timelineId = '1e10f150-949b-11ea-b63c-2bc51864784c'; const defaultProps = { - content: `A link to a timeline [timeline](http://localhost:5601/app/siem#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`, + content: `A link to a timeline [timeline](http://localhost:5601/app/security#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`, id: 'markdown-id', isEditable: false, onChangeEditable, diff --git a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx index 936bd26adff7f..687d2a36da610 100644 --- a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx @@ -14,6 +14,7 @@ import { LocalizedDateTooltip } from '../localized_date_tooltip'; import { getMaybeDate } from './maybe_date'; export const PreferenceFormattedDate = React.memo<{ dateFormat?: string; value: Date }>( + /* eslint-disable-next-line react-hooks/rules-of-hooks */ ({ value, dateFormat = useDateFormat() }) => ( <>{moment.tz(value, useTimeZone()).format(dateFormat)} ) diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx index 153a1703059c2..3451ddacb6538 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx @@ -9,7 +9,8 @@ import { mount } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { ModalInspectQuery } from './modal'; +import { NO_ALERT_INDEX } from '../../../../common/constants'; +import { ModalInspectQuery, formatIndexPatternRequested } from './modal'; const request = '{"index": ["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"allowNoIndices": true, "ignoreUnavailable": true, "body": { "aggregations": {"hosts": {"cardinality": {"field": "host.name" } }, "hosts_histogram": {"auto_date_histogram": {"field": "@timestamp","buckets": "6"},"aggs": { "count": {"cardinality": {"field": "host.name" }}}}}, "query": {"bool": {"filter": [{"range": { "@timestamp": {"gte": 1562290224506,"lte": 1562376624506 }}}]}}, "size": 0, "track_total_hits": false}}'; @@ -244,4 +245,31 @@ describe('Modal Inspect', () => { expect(closeModal).toHaveBeenCalled(); }); }); + + describe('formatIndexPatternRequested', () => { + test('Return specific messages to NO_ALERT_INDEX if we only have one index and we match the index name `NO_ALERT_INDEX`', () => { + const expected = formatIndexPatternRequested([NO_ALERT_INDEX]); + expect(expected).toEqual({'No alert index found'}); + }); + + test('Ignore NO_ALERT_INDEX if you have more than one indices', () => { + const expected = formatIndexPatternRequested([NO_ALERT_INDEX, 'indice-1']); + expect(expected).toEqual('indice-1'); + }); + + test('Happy path', () => { + const expected = formatIndexPatternRequested(['indice-1, indice-2']); + expect(expected).toEqual('indice-1, indice-2'); + }); + + test('Empty array with no indices', () => { + const expected = formatIndexPatternRequested([]); + expect(expected).toEqual('Sorry about that, something went wrong.'); + }); + + test('Undefined indices', () => { + const expected = formatIndexPatternRequested(undefined); + expect(expected).toEqual('Sorry about that, something went wrong.'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index 1563c005af5b6..e9f7edf86d4ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -22,6 +22,7 @@ import numeral from '@elastic/numeral'; import React, { ReactNode } from 'react'; import styled from 'styled-components'; +import { NO_ALERT_INDEX } from '../../../../common/constants'; import * as i18n from './translations'; const DescriptionListStyled = styled(EuiDescriptionList)` @@ -88,6 +89,15 @@ const manageStringify = (object: Record | Response): string => } }; +export const formatIndexPatternRequested = (indices: string[] = []) => { + if (indices.length === 1 && indices[0] === NO_ALERT_INDEX) { + return {i18n.NO_ALERT_INDEX_FOUND}; + } + return indices.length > 0 + ? indices.filter((i) => i !== NO_ALERT_INDEX).join(', ') + : i18n.SOMETHING_WENT_WRONG; +}; + export const ModalInspectQuery = ({ closeModal, isShowing = false, @@ -113,7 +123,7 @@ export const ModalInspectQuery = ({ ), description: ( - {inspectRequest != null ? inspectRequest.index.join(', ') : i18n.SOMETHING_WENT_WRONG} + {formatIndexPatternRequested(inspectRequest?.index ?? [])} ), }, diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts b/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts index c51423087911f..4a8da8050dd9a 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts @@ -60,3 +60,10 @@ export const REQUEST_TIMESTAMP_DESC = i18n.translate( defaultMessage: 'Time when the start of the request has been logged', } ); + +export const NO_ALERT_INDEX_FOUND = i18n.translate( + 'xpack.securitySolution.inspect.modal.noAlertIndexFound', + { + defaultMessage: 'No alert index found', + } +); diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx b/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx index e2d222e3b836b..937e3727ca613 100644 --- a/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx +++ b/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx @@ -34,6 +34,7 @@ const AlertDetailsOverviewComponent = memo(() => { return null; } + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const tabs: EuiTabbedContentTab[] = useMemo(() => { return [ { @@ -71,11 +72,13 @@ const AlertDetailsOverviewComponent = memo(() => { ]; }, [alertDetailsData]); + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const activeTab = useMemo( () => (alertDetailsTabId ? tabs.find(({ id }) => id === alertDetailsTabId) : tabs[0]), [alertDetailsTabId, tabs] ); + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const handleTabClick = useCallback( (clickedTab: EuiTabbedContentTab): void => { if (clickedTab.id !== alertDetailsTabId) { diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index 5cbfe2275d31c..579c3311cf732 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -22,6 +22,7 @@ export const FirstLastSeenHost = React.memo<{ hostname: string; type: FirstLastS return ( {(client) => { + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const { loading, firstSeen, lastSeen, errorMessage } = useFirstLastSeenHostQuery( hostname, 'default', diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx index 8172165a77e78..8db95f586782c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx @@ -136,8 +136,14 @@ export const PolicyResponse = memo( const attentionCount = responseAttentionCount.get(key); return ( htmlIdGenerator()(), [])} - key={useMemo(() => htmlIdGenerator()(), [])} + id={ + /* eslint-disable-next-line react-hooks/rules-of-hooks */ + useMemo(() => htmlIdGenerator()(), []) + } + key={ + /* eslint-disable-next-line react-hooks/rules-of-hooks */ + useMemo(() => htmlIdGenerator()(), []) + } data-test-subj="hostDetailsPolicyResponseConfigAccordion" buttonContent={ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx index e75f87e0d6011..77bd9aeba3ed2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx @@ -30,6 +30,7 @@ const TimelineKqlFetchComponent = memo( inputId, inspect: null, loading: false, + /* eslint-disable-next-line react-hooks/rules-of-hooks */ refetch: useUpdateKql({ indexPattern, kueryFilterQuery, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx index ede8d7cfded58..1038ac4b69587 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx @@ -18,7 +18,7 @@ const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf(); describe('Build KQL Query', () => { test('Build KQL query with one data provider', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 1"'); }); @@ -56,18 +56,40 @@ describe('Build KQL Query', () => { }); test('Build KQL query with two data provider', () => { - const dataProviders = mockDataProviders.slice(0, 2); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('(name : "Provider 1") or (name : "Provider 2")'); + }); + + test('Build KQL query with two data provider and first is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].enabled = false; + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 2"'); + }); + + test('Build KQL query with two data provider and second is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[1].enabled = false; const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); - expect(cleanUpKqlQuery(kqlQuery)).toEqual('(name : "Provider 1" ) or (name : "Provider 2" )'); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 1"'); }); test('Build KQL query with one data provider and one and', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].and = mockDataProviders.slice(1, 2); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(1, 2)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 1" and name : "Provider 2"'); }); + test('Build KQL query with one disabled data provider and one and', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].enabled = false; + dataProviders[0].and = cloneDeep(mockDataProviders.slice(1, 2)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 2"'); + }); + test('Build KQL query with one data provider and one and as timestamp (string input)', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); dataProviders[0].and = cloneDeep(mockDataProviders.slice(1, 2)); @@ -106,28 +128,50 @@ describe('Build KQL Query', () => { test('Build KQL query with two data provider and multiple and', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual( '(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5")' ); }); + test('Build KQL query with two data provider and multiple and and first data provider is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].enabled = false; + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual( + '(name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5")' + ); + }); + + test('Build KQL query with two data provider and multiple and and first and provider is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[0].and[0].enabled = false; + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual( + '(name : "Provider 1" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5")' + ); + }); + test('Build KQL query with all data provider', () => { const kqlQuery = buildGlobalQuery(mockDataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual( - '(name : "Provider 1" ) or (name : "Provider 2" ) or (name : "Provider 3" ) or (name : "Provider 4" ) or (name : "Provider 5" ) or (name : "Provider 6" ) or (name : "Provider 7" ) or (name : "Provider 8" ) or (name : "Provider 9" ) or (name : "Provider 10" )' + '(name : "Provider 1") or (name : "Provider 2") or (name : "Provider 3") or (name : "Provider 4") or (name : "Provider 5") or (name : "Provider 6") or (name : "Provider 7") or (name : "Provider 8") or (name : "Provider 9") or (name : "Provider 10")' ); }); test('Build complex KQL query with and and or', () => { const dataProviders = cloneDeep(mockDataProviders); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual( - '(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5") or (name : "Provider 3" ) or (name : "Provider 4" ) or (name : "Provider 5" ) or (name : "Provider 6" ) or (name : "Provider 7" ) or (name : "Provider 8" ) or (name : "Provider 9" ) or (name : "Provider 10" )' + '(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5") or (name : "Provider 3") or (name : "Provider 4") or (name : "Provider 5") or (name : "Provider 6") or (name : "Provider 7") or (name : "Provider 8") or (name : "Provider 9") or (name : "Provider 10")' ); }); }); @@ -223,7 +267,7 @@ describe('Combined Queries', () => { }); test('Only Data Provider', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -338,7 +382,7 @@ describe('Combined Queries', () => { }); test('Data Provider & KQL search query', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -356,7 +400,7 @@ describe('Combined Queries', () => { }); test('Data Provider & KQL filter query', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -375,8 +419,8 @@ describe('Combined Queries', () => { test('Data Provider & KQL search query multiple', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -395,8 +439,8 @@ describe('Combined Queries', () => { test('Data Provider & KQL filter query multiple', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const { filterQuery } = combineQueries({ config, dataProviders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index da74d22575f85..b5481e9d4eee2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -63,35 +63,30 @@ const buildQueryMatch = ( : `${dataProvider.queryMatch.field} ${EXISTS_OPERATOR}` }`.trim(); -const buildQueryForAndProvider = ( - dataAndProviders: DataProvidersAnd[], - browserFields: BrowserFields -) => - dataAndProviders - .reduce((andQuery, andDataProvider) => { - const prepend = (q: string) => `${q !== '' ? `${q} and ` : ''}`; - return andDataProvider.enabled - ? `${prepend(andQuery)} ${buildQueryMatch(andDataProvider, browserFields)}` - : andQuery; - }, '') - .trim(); - export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: BrowserFields) => dataProviders - .reduce((query, dataProvider: DataProvider, i) => { - const prepend = (q: string) => `${q !== '' ? `${q} or ` : ''}`; - const openParen = i >= 0 && dataProviders.length > 1 ? '(' : ''; - const closeParen = i >= 0 && dataProviders.length > 1 ? ')' : ''; - return dataProvider.enabled - ? `${prepend(query)}${openParen}${buildQueryMatch(dataProvider, browserFields)} - ${ - dataProvider.and.length > 0 - ? ` and ${buildQueryForAndProvider(dataProvider.and, browserFields)}` - : '' - }${closeParen}`.trim() - : query; - }, '') - .trim(); + .reduce((queries: string[], dataProvider: DataProvider) => { + const flatDataProviders = [dataProvider, ...dataProvider.and]; + const activeDataProviders = flatDataProviders.filter( + (flatDataProvider) => flatDataProvider.enabled + ); + + if (!activeDataProviders.length) return queries; + + const activeDataProvidersQueries = activeDataProviders.map((activeDataProvider) => + buildQueryMatch(activeDataProvider, browserFields) + ); + + const activeDataProvidersQueryMatch = activeDataProvidersQueries.join(' and '); + + return [...queries, activeDataProvidersQueryMatch]; + }, []) + .filter((queriesItem) => !isEmpty(queriesItem)) + .reduce((globalQuery: string, queryMatch: string, index: number, queries: string[]) => { + if (queries.length <= 1) return queryMatch; + + return !index ? `(${queryMatch})` : `${globalQuery} or (${queryMatch})`; + }, ''); export const combineQueries = ({ config, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx new file mode 100644 index 0000000000000..581fa125d21e2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { act } from 'react-dom/test-utils'; +import useResizeObserver from 'use-resize-observer/polyfilled'; + +import { + useSignalIndex, + ReturnSignalIndex, +} from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; +import { mocksSource } from '../../../common/containers/source/mock'; +import { wait } from '../../../common/lib/helpers'; +import { defaultHeaders, mockTimelineData, TestProviders } from '../../../common/mock'; +import { Direction } from '../../../graphql/types'; +import { timelineQuery } from '../../containers/index.gql_query'; +import { timelineActions } from '../../store/timeline'; + +import { Sort } from './body/sort'; +import { mockDataProviders } from './data_providers/mock/mock_data_providers'; +import { StatefulTimeline, Props as StatefulTimelineProps } from './index'; +import { Timeline } from './timeline'; + +jest.mock('../../../common/lib/kibana'); + +const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; +jest.mock('use-resize-observer/polyfilled'); +mockUseResizeObserver.mockImplementation(() => ({})); + +const mockUseSignalIndex: jest.Mock = useSignalIndex as jest.Mock; +jest.mock('../../../alerts/containers/detection_engine/alerts/use_signal_index'); + +describe('StatefulTimeline', () => { + let props = {} as StatefulTimelineProps; + const sort: Sort = { + columnId: '@timestamp', + sortDirection: Direction.desc, + }; + const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf(); + const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf(); + + const mocks = [ + { request: { query: timelineQuery }, result: { data: { events: mockTimelineData } } }, + ...mocksSource, + ]; + + beforeEach(() => { + props = { + addProvider: timelineActions.addProvider, + columns: defaultHeaders, + createTimeline: timelineActions.createTimeline, + dataProviders: mockDataProviders, + eventType: 'raw', + end: endDate, + filters: [], + id: 'foo', + isLive: false, + itemsPerPage: 5, + itemsPerPageOptions: [5, 10, 20], + kqlMode: 'search', + kqlQueryExpression: '', + onClose: jest.fn(), + onDataProviderEdited: timelineActions.dataProviderEdited, + removeColumn: timelineActions.removeColumn, + removeProvider: timelineActions.removeProvider, + show: true, + showCallOutUnauthorizedMsg: false, + sort, + start: startDate, + updateColumns: timelineActions.updateColumns, + updateDataProviderEnabled: timelineActions.updateDataProviderEnabled, + updateDataProviderExcluded: timelineActions.updateDataProviderExcluded, + updateDataProviderKqlQuery: timelineActions.updateDataProviderKqlQuery, + updateHighlightedDropAndProviderId: timelineActions.updateHighlightedDropAndProviderId, + updateItemsPerPage: timelineActions.updateItemsPerPage, + updateItemsPerPageOptions: timelineActions.updateItemsPerPageOptions, + updateSort: timelineActions.updateSort, + upsertColumn: timelineActions.upsertColumn, + usersViewing: ['elastic'], + }; + }); + + describe('indexToAdd', () => { + test('Make sure that indexToAdd return an unknown index if signalIndex does not exist', async () => { + mockUseSignalIndex.mockImplementation(() => ({ + loading: false, + signalIndexExists: false, + signalIndexName: undefined, + })); + const wrapper = mount( + + + + + + ); + await act(async () => { + await wait(); + wrapper.update(); + const timeline = wrapper.find(Timeline); + expect(timeline.props().indexToAdd).toEqual([ + 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51', + ]); + }); + }); + + test('Make sure that indexToAdd return siem signal index if signalIndex exist', async () => { + mockUseSignalIndex.mockImplementation(() => ({ + loading: false, + signalIndexExists: true, + signalIndexName: 'mock-siem-signals-index', + })); + const wrapper = mount( + + + + + + ); + await act(async () => { + await wait(); + wrapper.update(); + const timeline = wrapper.find(Timeline); + expect(timeline.props().indexToAdd).toEqual(['mock-siem-signals-index']); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index c52be64f94bf1..42fd6422d3a38 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -8,6 +8,7 @@ import React, { useEffect, useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; +import { NO_ALERT_INDEX } from '../../../../common/constants'; import { WithSource } from '../../../common/containers/source'; import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; @@ -30,7 +31,7 @@ export interface OwnProps { usersViewing: string[]; } -type Props = OwnProps & PropsFromRedux; +export type Props = OwnProps & PropsFromRedux; const StatefulTimelineComponent = React.memo( ({ @@ -67,11 +68,11 @@ const StatefulTimelineComponent = React.memo( eventType && signalIndexExists && signalIndexName != null && - ['signal', 'all'].includes(eventType) + ['signal', 'alert', 'all'].includes(eventType) ) { return [signalIndexName]; } - return []; + return [NO_ALERT_INDEX]; // Following index does not exist so we won't show any events; }, [eventType, signalIndexExists, signalIndexName]); const onDataProviderRemoved: OnDataProviderRemoved = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx index 5a3805af0ca43..b0682290ee849 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -79,7 +79,7 @@ const PickEventTypeComponents: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 5efcb84539123..7363a60974275 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -92,7 +92,7 @@ class TimelineQueryComponent extends QueryTemplate< indexPattern == null || (indexPattern != null && indexPattern.title === '') ? [ ...(['all', 'raw'].includes(eventType) ? defaultKibanaIndex : []), - ...(['all', 'signal'].includes(eventType) ? indexToAdd : []), + ...(['all', 'alert', 'signal'].includes(eventType) ? indexToAdd : []), ] : indexPattern?.title.split(',') ?? []; const variables: GetTimelineQuery.Variables = { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index f7e848e8a9e1b..caad70226365a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -18,7 +18,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/t export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; -export type EventType = 'all' | 'raw' | 'alert'; +export type EventType = 'all' | 'raw' | 'alert' | 'signal'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts index 759b0606a5e8b..cf181a78efcb8 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts @@ -68,14 +68,18 @@ export class SpacesService { return spaceId; }; + const internalRepositoryPromise = getStartServices().then(([coreStart]) => + coreStart.savedObjects.createInternalRepository(['space']) + ); + const getScopedClient = async (request: KibanaRequest) => { const [coreStart] = await getStartServices(); + const internalRepository = await internalRepositoryPromise; return config$ .pipe( + take(1), map((config) => { - const internalRepository = coreStart.savedObjects.createInternalRepository(['space']); - const callWithRequestRepository = coreStart.savedObjects.createScopedRepository( request, ['space'] @@ -92,8 +96,7 @@ export class SpacesService { internalRepository, request ); - }), - take(1) + }) ) .toPromise(); }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index b48deb771c873..67be9fb7ca793 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -30,6 +30,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm const [options, setOptions] = useState([]); const [isLoading, setIsLoading] = useState(true); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const fetchOptions = useCallback( debounce(async (searchValue: string) => { const esSearchRequest = { diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 5d9bbacb49006..d3a67f81004da 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -56,7 +56,7 @@ export class UptimePlugin appRoute: '/app/uptime#/', id: PLUGIN.ID, euiIconType: 'uptimeApp', - order: 8900, + order: 8400, title: PLUGIN.TITLE, category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts index cbf1f3e1af2df..d1acbf436bbe7 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts @@ -69,7 +69,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`allows settings to be changed`, async () => { @@ -125,7 +125,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`does not allow settings to be changed`, async () => { @@ -140,7 +140,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // FLAKY: https://github.com/elastic/kibana/issues/57377 describe.skip('no advanced_settings privileges', function () { - this.tags(['skipCoverage']); before(async () => { await security.role.create('no_advanced_settings_privileges_role', { elasticsearch: { @@ -178,7 +177,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`does not allow navigation to advanced settings; redirects to management home`, async () => { diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts index f7991e62fdaa9..c8adb3ce67d55 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts @@ -60,7 +60,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); describe('space with Advanced Settings disabled', function () { - this.tags('skipCoverage'); before(async () => { // we need to load the following in every situation as deleting // a space deletes all of the associated saved objects diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index aa12543004656..4c3c1556d621c 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -60,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['APM', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('APM'); }); it('can navigate to APM app', async () => { @@ -109,7 +109,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['APM', 'Stack Management']); + expect(navLinks).to.contain('APM'); }); it('can navigate to APM app', async () => { diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index e9fa4ccf8e48b..b776d358b1673 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -66,7 +66,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Canvas', 'Stack Management']); + expect(navLinks).to.contain('Canvas'); }); it(`landing page shows "Create new workpad" button`, async () => { @@ -142,7 +142,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Canvas', 'Stack Management']); + expect(navLinks).to.contain('Canvas'); }); it(`landing page shows disabled "Create new workpad" button`, async () => { diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts index 803ff6399a035..5d5f6b8aaa324 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts @@ -63,7 +63,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Dev Tools navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Dev Tools', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Dev Tools'); }); describe('console', () => { @@ -144,7 +144,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`shows 'Dev Tools' navlink`, async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Dev Tools', 'Stack Management']); + expect(navLinks).to.contain('Dev Tools'); }); describe('console', () => { diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/index.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/index.ts index 214a462447ef1..1df48971ba8cc 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/index.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('feature controls', function () { - this.tags(['skipFirefox', 'skipCoverage']); + this.tags(['skipFirefox']); loadTestFile(require.resolve('./dev_tools_security')); loadTestFile(require.resolve('./dev_tools_spaces')); }); diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 03a5cc6ac8fa0..6a11daa8d2c26 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -82,7 +82,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Discover', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Discover'); }); it('shows save button', async () => { @@ -169,7 +169,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Discover'); }); it(`doesn't show save button`, async () => { @@ -260,7 +260,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Discover'); }); it(`doesn't show save button`, async () => { diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts index 9121028c14404..f13d73bc95dbe 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Graph', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Graph'); }); it('landing page shows "Create new graph" button', async () => { @@ -127,7 +127,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Graph', 'Stack Management']); + expect(navLinks).to.contain('Graph'); }); it('does not show a "Create new Workspace" button', async () => { diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index cd892c4424290..a6d2c13cd2b31 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -71,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing shows create button`, async () => { @@ -125,7 +125,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing doesn't show create button`, async () => { @@ -177,7 +177,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`doesn't show Index Patterns in management side-nav`, async () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 6e186fc9ab9b2..a15b2b33b229c 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -61,7 +61,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows metrics navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Metrics', 'Stack Management']); + expect(navLinks).to.contain('Metrics'); }); describe('infrastructure landing page without data', () => { @@ -177,7 +177,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows metrics navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Metrics', 'Stack Management']); + expect(navLinks).to.contain('Metrics'); }); describe('infrastructure landing page without data', () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index fafc88287a6ab..ce83a22fb2e1f 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -58,7 +58,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows logs navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Logs', 'Stack Management']); + expect(navLinks).to.contain('Logs'); }); describe('logs landing page without data', () => { @@ -121,7 +121,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows logs navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Logs', 'Stack Management']); + expect(navLinks).to.contain('Logs'); }); describe('logs landing page without data', () => { diff --git a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts index 2449430ac85c2..f1c5b3f82f7da 100644 --- a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts +++ b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts @@ -66,7 +66,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Maps', 'Stack Management']); + expect(navLinks).to.contain('Maps'); }); it(`allows a map to be created`, async () => { @@ -153,7 +153,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Maps', 'Stack Management']); + expect(navLinks).to.contain('Maps'); }); it(`does not show create new button`, async () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index d7565fd846e70..c8baa13b56408 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -11,7 +11,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('classification creation', function () { + // flaky test, see https://github.com/elastic/kibana/issues/68356 + describe.skip('classification creation', function () { before(async () => { await esArchiver.loadIfNeeded('ml/bm_classification'); await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp'); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 7c59664fdec35..c818619a18378 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -11,7 +11,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('regression creation', function () { + // flaky test, see https://github.com/elastic/kibana/issues/68352 + describe.skip('regression creation', function () { before(async () => { await esArchiver.loadIfNeeded('ml/egs_regression'); await ml.testResources.createIndexPatternIfNeeded('ft_egs_regression', '@timestamp'); diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts index a3ade23f5c178..5021bd8cce0fc 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts @@ -60,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows timelion navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Timelion', 'Stack Management']); + expect(navLinks).to.contain('Timelion'); }); it(`allows a timelion sheet to be created`, async () => { @@ -112,7 +112,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows timelion navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Timelion', 'Stack Management']); + expect(navLinks).to.contain('Timelion'); }); it(`does not allow a timelion sheet to be created`, async () => { diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index 0ae56a3c5f256..c7ba7816e0255 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -14,15 +14,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/66869 - describe.skip('certificates', function () { - beforeEach(async () => { + describe('certificates', function () { + before(async () => { + await makeCheck({ es, tls: true }); await uptime.goToRoot(true); + }); + + beforeEach(async () => { await makeCheck({ es, tls: true }); - await uptimeService.navigation.refreshApp(); }); it('can navigate to cert page', async () => { + await uptimeService.cert.isUptimeDataMissing(); await uptimeService.cert.hasViewCertButton(); await uptimeService.navigation.goToCertificates(); }); @@ -30,6 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('page', () => { beforeEach(async () => { await uptimeService.navigation.goToCertificates(); + await uptimeService.navigation.refreshApp(); }); it('displays certificates', async () => { diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts index ae13cf0742432..991cd07dce513 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Uptime', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Uptime'); }); it('can navigate to Uptime app', async () => { @@ -115,7 +115,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Uptime', 'Stack Management']); + expect(navLinks).to.contain('Uptime'); }); it('can navigate to Uptime app', async () => { diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts index 9410a6f9435f2..f74643939477c 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts @@ -77,7 +77,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Visualize', 'Stack Management']); + expect(navLinks).to.contain('Visualize'); }); it(`landing page shows "Create new Visualization" button`, async () => { @@ -201,7 +201,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Visualize', 'Stack Management']); + expect(navLinks).to.contain('Visualize'); }); it(`landing page shows "Create new Visualization" button`, async () => { @@ -316,7 +316,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Visualize', 'Stack Management']); + expect(navLinks).to.contain('Visualize'); }); it(`landing page shows "Create new Visualization" button`, async () => { diff --git a/x-pack/test/functional/apps/visualize/index.ts b/x-pack/test/functional/apps/visualize/index.ts index 1c23b8cde8606..0f64ecf0022d0 100644 --- a/x-pack/test/functional/apps/visualize/index.ts +++ b/x-pack/test/functional/apps/visualize/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function visualize({ loadTestFile }: FtrProviderContext) { describe('Visualize', function visualizeTestSuite() { - this.tags(['ciGroup4', 'skipFirefox', 'skipCoverage']); + this.tags(['ciGroup4', 'skipFirefox']); loadTestFile(require.resolve('./feature_controls/visualize_security')); loadTestFile(require.resolve('./feature_controls/visualize_spaces')); diff --git a/x-pack/test/functional/services/uptime/certificates.ts b/x-pack/test/functional/services/uptime/certificates.ts index fb7cb6191b0ae..2ceab1ca89e54 100644 --- a/x-pack/test/functional/services/uptime/certificates.ts +++ b/x-pack/test/functional/services/uptime/certificates.ts @@ -17,31 +17,49 @@ export function UptimeCertProvider({ getService }: FtrProviderContext) { await input.type(text); }; + const refreshApp = async () => { + await testSubjects.click('superDatePickerApplyTimeButton', 10000); + }; + return { + async isUptimeDataMissing() { + return retry.tryForTime(60 * 1000, async () => { + if (await testSubjects.exists('data-missing', { timeout: 0 })) { + await refreshApp(); + } + await testSubjects.missingOrFail('data-missing'); + }); + }, async hasViewCertButton() { return retry.tryForTime(15000, async () => { await testSubjects.existOrFail('uptimeCertificatesLink'); }); }, async certificateExists(cert: { certId: string; monitorId: string }) { - return retry.tryForTime(15000, async () => { + return retry.tryForTime(60 * 1000, async () => { + if (!(await testSubjects.exists(cert.certId))) { + await refreshApp(); + } await testSubjects.existOrFail(cert.certId); await testSubjects.existOrFail('monitor-page-link-' + cert.monitorId); }); }, async hasCertificates(expectedTotal?: number) { - return retry.tryForTime(15000, async () => { + return retry.tryForTime(60 * 1000, async () => { const totalCerts = await testSubjects.getVisibleText('uptimeCertTotal'); if (expectedTotal) { - expect(Number(totalCerts) === expectedTotal).to.eql(true); + expect(Number(totalCerts)).to.eql(expectedTotal); } else { + if (Number(totalCerts) < 1) { + await refreshApp(); + } expect(Number(totalCerts) > 0).to.eql(true); } }); }, async searchIsWorking(monId: string) { const self = this; - return retry.tryForTime(15000, async () => { + return retry.tryForTime(60 * 1000, async () => { await changeSearchField(monId); await self.hasCertificates(1); }); diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index d372bd53c081b..7c5a4632f8627 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -26,7 +26,7 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }; const refreshApp = async () => { - await testSubjects.click('superDatePickerApplyTimeButton'); + await testSubjects.click('superDatePickerApplyTimeButton', 10000); }; return { @@ -65,10 +65,15 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, goToCertificates: async () => { - return retry.try(async () => { - await testSubjects.click('uptimeCertificatesLink'); - await testSubjects.existOrFail('uptimeCertificatesPage'); - }); + if (!(await testSubjects.exists('uptimeCertificatesPage', { timeout: 0 }))) { + return retry.try(async () => { + if (await testSubjects.exists('uptimeCertificatesLink', { timeout: 0 })) { + await testSubjects.click('uptimeCertificatesLink', 10000); + } + await testSubjects.existOrFail('uptimeCertificatesPage'); + }); + } + return true; }, async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { diff --git a/x-pack/test/page_load_metrics/config.ts b/x-pack/test/page_load_metrics/config.ts new file mode 100644 index 0000000000000..641099ff8e934 --- /dev/null +++ b/x-pack/test/page_load_metrics/config.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; + +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { PuppeteerTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonTestsConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const xpackFunctionalTestsConfig = await readConfigFile( + require.resolve('../functional/config.js') + ); + + return { + ...kibanaCommonTestsConfig.getAll(), + + testRunner: PuppeteerTestRunner, + + esArchiver: { + directory: resolve(__dirname, 'es_archives'), + }, + + screenshots: { + directory: resolve(__dirname, 'screenshots'), + }, + + esTestCluster: { + ...xpackFunctionalTestsConfig.get('esTestCluster'), + serverArgs: [...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs')], + }, + + kbnTestServer: { + ...xpackFunctionalTestsConfig.get('kbnTestServer'), + }, + }; +} diff --git a/x-pack/test/page_load_metrics/es_archives/default/data.json.gz b/x-pack/test/page_load_metrics/es_archives/default/data.json.gz new file mode 100644 index 0000000000000..5a5290ddf6447 Binary files /dev/null and b/x-pack/test/page_load_metrics/es_archives/default/data.json.gz differ diff --git a/x-pack/test/page_load_metrics/es_archives/default/mappings.json b/x-pack/test/page_load_metrics/es_archives/default/mappings.json new file mode 100644 index 0000000000000..c36f9576c4df1 --- /dev/null +++ b/x-pack/test/page_load_metrics/es_archives/default/mappings.json @@ -0,0 +1,2402 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "bfd39d88aadadb4be597ea984d433dbe", + "metrics-explorer-view": "428e319af3e822c80a84cf87123ca35c", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "b675c3be8d76ecf029294d51dc7ec65d", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "properties": { + "agents": { + "properties": { + "dotnet": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "go": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "java": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "js-base": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "nodejs": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "python": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "ruby": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "rum-js": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "indexPatternsWithGeoPointFieldCount": { + "type": "long" + }, + "indexPatternsWithGeoShapeFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "type": "keyword" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "integer" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "type": "keyword" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "type": "keyword" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "test", + "mappings": { + "properties": { + "foo": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/page_load_metrics/runner.ts b/x-pack/test/page_load_metrics/runner.ts new file mode 100644 index 0000000000000..05f293730f843 --- /dev/null +++ b/x-pack/test/page_load_metrics/runner.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CiStatsReporter } from '@kbn/dev-utils'; +import { capturePageLoadMetrics } from '@kbn/test'; +// @ts-ignore not TS yet +import getUrl from '../../../src/test_utils/get_url'; + +import { FtrProviderContext } from './../functional/ftr_provider_context'; + +export async function PuppeteerTestRunner({ getService }: FtrProviderContext) { + const log = getService('log'); + const config = getService('config'); + const esArchiver = getService('esArchiver'); + + await esArchiver.load('default'); + const metrics = await capturePageLoadMetrics(log, { + headless: true, + appConfig: { + url: getUrl.baseUrl(config.get('servers.kibana')), + username: config.get('servers.kibana.username'), + password: config.get('servers.kibana.password'), + }, + screenshotsDir: config.get('screenshots.directory'), + }); + const reporter = CiStatsReporter.fromEnv(log); + + log.debug('Report page load asset size'); + await reporter.metrics(metrics); +} diff --git a/yarn.lock b/yarn.lock index 892fa1b5aa567..678ddfb052cd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4630,6 +4630,13 @@ dependencies: "@types/node" "*" +"@types/puppeteer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-3.0.0.tgz#24cdcc131e319477608d893f0017e08befd70423" + integrity sha512-59+fkfHHXHzX5rgoXIMnZyzum7ZLx/Wc3fhsOduFThpTpKbzzdBHMZsrkKGLunimB4Ds/tI5lXTRLALK8Mmnhg== + dependencies: + "@types/node" "*" + "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" @@ -7695,6 +7702,15 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" +bl@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" + integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blob@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -8208,6 +8224,14 @@ buffer@^5.1.0, buffer@^5.2.0: base64-js "^1.0.2" ieee754 "^1.1.4" +buffer@^5.2.1, buffer@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -12707,10 +12731,10 @@ eslint-plugin-prettier@^3.1.3: dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz#53e073961f1f5ccf8dd19558036c1fac8c29d99a" - integrity sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw== +eslint-plugin-react-hooks@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz#aed33b4254a41b045818cacb047b81e6df27fa58" + integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== eslint-plugin-react-perf@^3.2.3: version "3.2.3" @@ -16676,7 +16700,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -20841,6 +20865,11 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -23967,6 +23996,22 @@ puppeteer@^2.0.0: rimraf "^2.6.1" ws "^6.1.0" +puppeteer@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7" + integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw== + dependencies: + debug "^4.1.0" + extract-zip "^2.0.0" + https-proxy-agent "^4.0.0" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^3.0.2" + tar-fs "^2.0.0" + unbzip2-stream "^1.3.3" + ws "^7.2.3" + q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -26302,6 +26347,13 @@ rimraf@^2.5.4, rimraf@^2.7.1: dependencies: glob "^7.1.3" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rimraf@~2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.0.3.tgz#f50a2965e7144e9afd998982f15df706730f56a9" @@ -28654,6 +28706,16 @@ tar-fs@^1.16.3: pump "^1.0.0" tar-stream "^1.1.2" +tar-fs@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" + integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + tar-stream@^1.1.2, tar-stream@^1.5.2: version "1.5.5" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" @@ -28664,6 +28726,17 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: readable-stream "^2.0.0" xtend "^4.0.0" +tar-stream@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" + integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== + dependencies: + bl "^4.0.1" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar-stream@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" @@ -28962,7 +29035,7 @@ through2@~2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4, through@~2.3.6, through@~2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.6, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -30151,6 +30224,14 @@ unbzip2-stream@^1.0.9: buffer "^3.0.1" through "^2.3.6" +unbzip2-stream@^1.3.3: + version "1.4.2" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf" + integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -32015,6 +32096,11 @@ ws@^7.0.0: dependencies: async-limiter "^1.0.0" +ws@^7.2.3: + version "7.3.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" + integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== + ws@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"