diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy index ed642f22cfeb4..97099c6f87448 100644 --- a/.ci/end2end.groovy +++ b/.ci/end2end.groovy @@ -14,6 +14,7 @@ pipeline { HOME = "${env.WORKSPACE}" E2E_DIR = 'x-pack/plugins/apm/e2e' PIPELINE_LOG_LEVEL = 'DEBUG' + KBN_OPTIMIZER_THEMES = 'v7light' } options { timeout(time: 1, unit: 'HOURS') diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4cc0c8016f1d0..754043ee0ef77 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,7 +11,7 @@ Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios - [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server) -- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) +- [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 211cfac3806ad..1466865df8d98 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -1,71 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`builds expected bundles, saves bundle counts to metadata: OptimizerConfig 1`] = ` -OptimizerConfig { - "bundles": Array [ - Bundle { - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, - "id": "bar", - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - Bundle { - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, - "id": "foo", - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - ], - "cache": true, - "dist": false, - "inspectWorkers": false, - "maxWorkerCount": 1, - "plugins": Array [ - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, - "extraPublicDirs": Array [], - "id": "bar", - "isUiPlugin": true, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, - "extraPublicDirs": Array [], - "id": "foo", - "isUiPlugin": true, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/nested/baz, - "extraPublicDirs": Array [], - "id": "baz", - "isUiPlugin": false, - }, - ], - "profileWebpack": false, - "repoRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "themeTags": Array [ - "v7dark", - "v7light", - ], - "watch": false, -} -`; - exports[`prepares assets for distribution: bar bundle 1`] = `"(function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!==\\"undefined\\"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:\\"Module\\"})}Object.defineProperty(exports,\\"__esModule\\",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value===\\"object\\"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,\\"default\\",{enumerable:true,value:value});if(mode&2&&typeof value!=\\"string\\")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module[\\"default\\"]}:function getModuleExports(){return module};__webpack_require__.d(getter,\\"a\\",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p=\\"\\";return __webpack_require__(__webpack_require__.s=5)})([function(module,exports,__webpack_require__){\\"use strict\\";var isOldIE=function isOldIE(){var memo;return function memorize(){if(typeof memo===\\"undefined\\"){memo=Boolean(window&&document&&document.all&&!window.atob)}return memo}}();var getTarget=function getTarget(){var memo={};return function memorize(target){if(typeof memo[target]===\\"undefined\\"){var styleTarget=document.querySelector(target);if(window.HTMLIFrameElement&&styleTarget instanceof window.HTMLIFrameElement){try{styleTarget=styleTarget.contentDocument.head}catch(e){styleTarget=null}}memo[target]=styleTarget}return memo[target]}}();var stylesInDom=[];function getIndexByIdentifier(identifier){var result=-1;for(var i=0;i { await del(TMP_DIR); }); -it('builds expected bundles, saves bundle counts to metadata', async () => { +// FLAKY: https://github.com/elastic/kibana/issues/70762 +it.skip('builds expected bundles, saves bundle counts to metadata', async () => { const config = OptimizerConfig.create({ repoRoot: MOCK_REPO_DIR, pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')], @@ -167,7 +168,8 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { `); }); -it('uses cache on second run and exist cleanly', async () => { +// FLAKY: https://github.com/elastic/kibana/issues/70764 +it.skip('uses cache on second run and exist cleanly', async () => { const config = OptimizerConfig.create({ repoRoot: MOCK_REPO_DIR, pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')], diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx index 302477a5fff5e..561c33519f96f 100644 --- a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx @@ -56,7 +56,7 @@ export function NoDataPopover({

{i18n.translate('data.noDataPopover.content', { defaultMessage: - "This time range doesn't contain any data. Increase or adjust the time range to see more fields and create charts", + "This time range doesn't contain any data. Increase or adjust the time range to see more fields and create charts.", })}

@@ -66,11 +66,13 @@ export function NoDataPopover({ step={1} stepsTotal={1} isStepOpen={noDataPopoverVisible} - subtitle={i18n.translate('data.noDataPopover.title', { defaultMessage: 'Tip' })} - title="" + subtitle={i18n.translate('data.noDataPopover.subtitle', { defaultMessage: 'Tip' })} + title={i18n.translate('data.noDataPopover.title', { defaultMessage: 'Empty dataset' })} footerAction={ { storage.set(NO_DATA_POPOVER_STORAGE_KEY, true); diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 4326200141179..102183fc1c5ed 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -16,8 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - -export { searchSavedObjectType } from './search'; export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telementry'; diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index df809b425eb9e..34ed8c6c6f401 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -27,7 +27,6 @@ import { } from './types'; import { registerSearchRoute } from './routes'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search'; -import { searchSavedObjectType } from '../saved_objects'; import { DataPluginStart } from '../plugin'; export class SearchService implements Plugin { @@ -36,8 +35,6 @@ export class SearchService implements Plugin { constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup): ISearchSetup { - core.savedObjects.registerType(searchSavedObjectType); - this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$) diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts index a7445a5189163..77553ce644839 100644 --- a/src/plugins/discover/server/plugin.ts +++ b/src/plugins/discover/server/plugin.ts @@ -20,11 +20,13 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/server'; import { uiSettings } from './ui_settings'; import { capabilitiesProvider } from './capabilities_provider'; +import { searchSavedObjectType } from './saved_objects'; export class DiscoverServerPlugin implements Plugin { public setup(core: CoreSetup) { core.capabilities.registerProvider(capabilitiesProvider); core.uiSettings.register(uiSettings); + core.savedObjects.registerType(searchSavedObjectType); return {}; } diff --git a/src/plugins/discover/server/saved_objects/index.ts b/src/plugins/discover/server/saved_objects/index.ts new file mode 100644 index 0000000000000..efe785364ccb6 --- /dev/null +++ b/src/plugins/discover/server/saved_objects/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { searchSavedObjectType } from './search'; diff --git a/src/plugins/data/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts similarity index 84% rename from src/plugins/data/server/saved_objects/search.ts rename to src/plugins/discover/server/saved_objects/search.ts index 16caaf05a0fc6..2348d89c4f4dd 100644 --- a/src/plugins/data/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -18,7 +18,7 @@ */ import { SavedObjectsType } from 'kibana/server'; -import { searchSavedObjectTypeMigrations } from './search_migrations'; +import { searchMigrations } from './search_migrations'; export const searchSavedObjectType: SavedObjectsType = { name: 'search', @@ -43,18 +43,18 @@ export const searchSavedObjectType: SavedObjectsType = { }, mappings: { properties: { - columns: { type: 'keyword' }, + columns: { type: 'keyword', index: false }, description: { type: 'text' }, - hits: { type: 'integer' }, + hits: { type: 'integer', index: false }, kibanaSavedObjectMeta: { properties: { - searchSourceJSON: { type: 'text' }, + searchSourceJSON: { type: 'text', index: false }, }, }, - sort: { type: 'keyword' }, + sort: { type: 'keyword', index: false }, title: { type: 'text' }, version: { type: 'integer' }, }, }, - migrations: searchSavedObjectTypeMigrations as any, + migrations: searchMigrations as any, }; diff --git a/src/plugins/data/server/saved_objects/search_migrations.test.ts b/src/plugins/discover/server/saved_objects/search_migrations.test.ts similarity index 96% rename from src/plugins/data/server/saved_objects/search_migrations.test.ts rename to src/plugins/discover/server/saved_objects/search_migrations.test.ts index 69db08a689255..babd25c03dbb2 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.test.ts @@ -18,13 +18,13 @@ */ import { SavedObjectMigrationContext } from 'kibana/server'; -import { searchSavedObjectTypeMigrations } from './search_migrations'; +import { searchMigrations } from './search_migrations'; const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; describe('migration search', () => { describe('6.7.2', () => { - const migrationFn = searchSavedObjectTypeMigrations['6.7.2']; + const migrationFn = searchMigrations['6.7.2']; it('should migrate obsolete match_all query', () => { const migratedDoc = migrationFn( @@ -56,7 +56,7 @@ describe('migration search', () => { }); describe('7.0.0', () => { - const migrationFn = searchSavedObjectTypeMigrations['7.0.0']; + const migrationFn = searchMigrations['7.0.0']; test('skips errors when searchSourceJSON is null', () => { const doc = { @@ -278,7 +278,7 @@ Object { }); describe('7.4.0', function () { - const migrationFn = searchSavedObjectTypeMigrations['7.4.0']; + const migrationFn = searchMigrations['7.4.0']; test('transforms one dimensional sort arrays into two dimensional arrays', () => { const doc = { diff --git a/src/plugins/data/server/saved_objects/search_migrations.ts b/src/plugins/discover/server/saved_objects/search_migrations.ts similarity index 97% rename from src/plugins/data/server/saved_objects/search_migrations.ts rename to src/plugins/discover/server/saved_objects/search_migrations.ts index 9bba429f8d71b..0302159c43c56 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.ts @@ -19,7 +19,7 @@ import { flow, get } from 'lodash'; import { SavedObjectMigrationFn } from 'kibana/server'; -import { DEFAULT_QUERY_LANGUAGE } from '../../common'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; const migrateMatchAllQuery: SavedObjectMigrationFn = (doc) => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -121,7 +121,7 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) = }; }; -export const searchSavedObjectTypeMigrations = { +export const searchMigrations = { '6.7.2': flow(migrateMatchAllQuery), '7.0.0': flow(setNewReferences), '7.4.0': flow(migrateSearchSortToNestedArray), diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index 2e83d16dd778e..4e73d27c1c4a1 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -475,17 +475,6 @@ describe('Execution', () => { } }); - test('sets duration to 10 milliseconds when function executes 10 milliseconds', async () => { - const execution = createExecution('sleep 10', {}, true); - execution.start(-1); - await execution.result; - - const node = execution.state.get().ast.chain[0]; - expect(typeof node.debug?.duration).toBe('number'); - expect(node.debug?.duration).toBeLessThan(50); - expect(node.debug?.duration).toBeGreaterThanOrEqual(5); - }); - test('adds .debug field in expression AST on each executed function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js index 1e310c1ddd268..5a30456bd59ab 100644 --- a/test/functional/apps/dashboard/index.js +++ b/test/functional/apps/dashboard/index.js @@ -49,6 +49,7 @@ export default function ({ getService, loadTestFile }) { after(unloadCurrentData); loadTestFile(require.resolve('./empty_dashboard')); + loadTestFile(require.resolve('./url_field_formatter')); loadTestFile(require.resolve('./embeddable_rendering')); loadTestFile(require.resolve('./create_and_add_embeddables')); loadTestFile(require.resolve('./edit_embeddable_redirects')); diff --git a/test/functional/apps/dashboard/url_field_formatter.ts b/test/functional/apps/dashboard/url_field_formatter.ts new file mode 100644 index 0000000000000..9b05b9b777b94 --- /dev/null +++ b/test/functional/apps/dashboard/url_field_formatter.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { common, dashboard, settings, timePicker, visChart } = getPageObjects([ + 'common', + 'dashboard', + 'settings', + 'timePicker', + 'visChart', + ]); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + const fieldName = 'clientip'; + + const clickFieldAndCheckUrl = async (fieldLink: WebElementWrapper) => { + const fieldValue = await fieldLink.getVisibleText(); + await fieldLink.click(); + const windowHandlers = await browser.getAllWindowHandles(); + expect(windowHandlers.length).to.equal(2); + await browser.switchToWindow(windowHandlers[1]); + const currentUrl = await browser.getCurrentUrl(); + const fieldUrl = common.getHostPort() + '/app/' + fieldValue; + expect(currentUrl).to.equal(fieldUrl); + }; + + describe('Changing field formatter to Url', () => { + before(async function () { + await esArchiver.load('dashboard/current/kibana'); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await common.navigateToApp('settings'); + await settings.clickKibanaIndexPatterns(); + await settings.clickIndexPatternLogstash(); + await settings.filterField(fieldName); + await settings.openControlsByName(fieldName); + await settings.setFieldFormat('url'); + await settings.controlChangeSave(); + }); + + it('applied on dashboard', async () => { + await common.navigateToApp('dashboard'); + await dashboard.loadSavedDashboard('dashboard with everything'); + await dashboard.waitForRenderComplete(); + const fieldLink = await visChart.getFieldLinkInVisTable(`${fieldName}: Descending`, 1); + await clickFieldAndCheckUrl(fieldLink); + }); + + it('applied on discover', async () => { + await common.navigateToApp('discover'); + await timePicker.setAbsoluteRange( + 'Sep 19, 2017 @ 06:31:44.000', + 'Sep 23, 2018 @ 18:31:44.000' + ); + await testSubjects.click('docTableExpandToggleColumn'); + const fieldLink = await testSubjects.find(`tableDocViewRow-${fieldName}-value`); + await clickFieldAndCheckUrl(fieldLink); + }); + + afterEach(async function () { + const windowHandlers = await browser.getAllWindowHandles(); + if (windowHandlers.length > 1) { + await browser.closeCurrentWindow(); + await browser.switchToWindow(windowHandlers[0]); + } + }); + }); +} diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 673fba0c346b8..590631ad48b00 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -302,6 +302,20 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr return element.getVisibleText(); } + public async getFieldLinkInVisTable(fieldName: string, rowIndex: number = 1) { + const tableVis = await testSubjects.find('tableVis'); + const $ = await tableVis.parseDomContent(); + const headers = $('span[ng-bind="::col.title"]') + .toArray() + .map((header: any) => $(header).text()); + const fieldColumnIndex = headers.indexOf(fieldName); + return await find.byCssSelector( + `[data-test-subj="paginated-table-body"] tr:nth-of-type(${rowIndex}) td:nth-of-type(${ + fieldColumnIndex + 1 + }) a` + ); + } + /** * If you are writing new tests, you should rather look into getTableVisContent method instead. * @deprecated Use getTableVisContent instead. diff --git a/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts b/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts index 689b88390810f..5791dfe5b9463 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts +++ b/x-pack/plugins/apm/e2e/cypress/integration/helpers.ts @@ -6,9 +6,6 @@ /* eslint-disable import/no-extraneous-dependencies */ -const RANGE_FROM = '2020-06-01T14:59:32.686Z'; -const RANGE_TO = '2020-06-16T16:59:36.219Z'; - const BASE_URL = Cypress.config().baseUrl; /** The default time in ms to wait for a Cypress command to complete */ @@ -16,20 +13,14 @@ export const DEFAULT_TIMEOUT = 60 * 1000; export function loginAndWaitForPage( url: string, - dateRange?: { to: string; from: string } + dateRange: { to: string; from: string } ) { const username = Cypress.env('elasticsearch_username'); const password = Cypress.env('elasticsearch_password'); cy.log(`Authenticating via ${username} / ${password}`); - let rangeFrom = RANGE_FROM; - let rangeTo = RANGE_TO; - if (dateRange) { - rangeFrom = dateRange.from; - rangeTo = dateRange.to; - } - - const fullUrl = `${BASE_URL}${url}?rangeFrom=${rangeFrom}&rangeTo=${rangeTo}`; + + const fullUrl = `${BASE_URL}${url}?rangeFrom=${dateRange.from}&rangeTo=${dateRange.to}`; cy.visit(fullUrl, { auth: { username, password } }); cy.viewport('macbook-15'); diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js index ac09e575a46ae..7fbce2583903c 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js +++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js @@ -1,12 +1,5 @@ module.exports = { - "__version": "4.5.0", - "APM": { - "Transaction duration charts": { - "1": "55 ms", - "2": "28 ms", - "3": "0 ms" - } - }, + "__version": "4.9.0", "RUM Dashboard": { "Client metrics": { "1": "55 ", diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts index 361d055db9ac1..c1402bbd035f4 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -12,7 +12,10 @@ export const DEFAULT_TIMEOUT = 60 * 1000; Given(`a user browses the APM UI application`, () => { // open service overview page - loginAndWaitForPage(`/app/apm#/services`); + loginAndWaitForPage(`/app/apm#/services`, { + from: '2020-06-01T14:59:32.686Z', + to: '2020-06-16T16:59:36.219Z', + }); }); When(`the user inspects the opbeans-node service`, () => { @@ -34,9 +37,8 @@ Then(`should have correct y-axis ticks`, () => { // wait for all loading to finish cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get(yAxisTick).eq(2).invoke('text').snapshot(); - - cy.get(yAxisTick).eq(1).invoke('text').snapshot(); - - cy.get(yAxisTick).eq(0).invoke('text').snapshot(); + // literal assertions because snapshot() doesn't retry + cy.get(yAxisTick).eq(2).should('have.text', '55 ms'); + cy.get(yAxisTick).eq(1).should('have.text', '28 ms'); + cy.get(yAxisTick).eq(0).should('have.text', '0 ms'); }); diff --git a/x-pack/plugins/apm/e2e/package.json b/x-pack/plugins/apm/e2e/package.json index 417dda4c5220e..5101e64235c62 100644 --- a/x-pack/plugins/apm/e2e/package.json +++ b/x-pack/plugins/apm/e2e/package.json @@ -9,19 +9,19 @@ }, "dependencies": { "@cypress/snapshot": "^2.1.3", - "@cypress/webpack-preprocessor": "^5.2.0", + "@cypress/webpack-preprocessor": "^5.4.1", "@types/cypress-cucumber-preprocessor": "^1.14.1", - "@types/node": "^14.0.1", + "@types/node": "^14.0.14", "axios": "^0.19.2", - "cypress": "^4.5.0", - "cypress-cucumber-preprocessor": "^2.3.1", + "cypress": "^4.9.0", + "cypress-cucumber-preprocessor": "^2.5.2", "ora": "^4.0.4", - "p-limit": "^2.3.0", + "p-limit": "^3.0.1", "p-retry": "^4.2.0", - "ts-loader": "^7.0.4", - "typescript": "3.9.5", - "wait-on": "^5.0.0", + "ts-loader": "^7.0.5", + "typescript": "3.9.6", + "wait-on": "^5.0.1", "webpack": "^4.43.0", - "yargs": "^15.3.1" + "yargs": "^15.4.0" } } diff --git a/x-pack/plugins/apm/e2e/run-e2e.sh b/x-pack/plugins/apm/e2e/run-e2e.sh index 43cc74a197f42..bc64f2b009d52 100755 --- a/x-pack/plugins/apm/e2e/run-e2e.sh +++ b/x-pack/plugins/apm/e2e/run-e2e.sh @@ -106,10 +106,12 @@ yarn &> ${TMP_DIR}/e2e-yarn.log echo "" # newline echo "${bold}Static mock data (logs: ${E2E_DIR}${TMP_DIR}/ingest-data.log)${normal}" +STATIC_MOCK_FILENAME='2020-06-12.json' + # Download static data if not already done -if [ ! -e "${TMP_DIR}/events.json" ]; then - echo 'Downloading events.json...' - curl --silent https://storage.googleapis.com/apm-ui-e2e-static-data/2020-06-12.json --output ${TMP_DIR}/events.json +if [ ! -e "${TMP_DIR}/${STATIC_MOCK_FILENAME}" ]; then + echo "Downloading ${STATIC_MOCK_FILENAME}..." + curl --silent https://storage.googleapis.com/apm-ui-e2e-static-data/${STATIC_MOCK_FILENAME} --output ${TMP_DIR}/${STATIC_MOCK_FILENAME} fi # echo "Deleting existing indices (apm* and .apm*)" @@ -117,7 +119,7 @@ curl --silent --user admin:changeme -XDELETE "localhost:${ELASTICSEARCH_PORT}/.a curl --silent --user admin:changeme -XDELETE "localhost:${ELASTICSEARCH_PORT}/apm*" > /dev/null # Ingest data into APM Server -node ingest-data/replay.js --server-url http://localhost:$APM_SERVER_PORT --events ${TMP_DIR}/events.json 2>> ${TMP_DIR}/ingest-data.log +node ingest-data/replay.js --server-url http://localhost:$APM_SERVER_PORT --events ${TMP_DIR}/${STATIC_MOCK_FILENAME} 2>> ${TMP_DIR}/ingest-data.log # Abort if not all events were ingested correctly if [ $? -ne 0 ]; then diff --git a/x-pack/plugins/apm/e2e/yarn.lock b/x-pack/plugins/apm/e2e/yarn.lock index 975154d71b85d..936294052aa7b 100644 --- a/x-pack/plugins/apm/e2e/yarn.lock +++ b/x-pack/plugins/apm/e2e/yarn.lock @@ -689,6 +689,14 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" +"@babel/runtime-corejs3@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" + integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + "@babel/runtime@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" @@ -802,13 +810,14 @@ snap-shot-compare "2.8.3" snap-shot-store "1.2.3" -"@cypress/webpack-preprocessor@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.2.0.tgz#3a17b478f6e2d600e536e6dda9c2e349d25a297e" - integrity sha512-uvo0FfKL+rIXrBGS6qPIaJRD8euK+t6YoZvrTuLPnStprzlgeGfSCnCDUEMJZqFk9LwBd1NtOop+J7qNuv74ng== +"@cypress/webpack-preprocessor@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.1.tgz#eb58f6cd02932a95653c1a674cfd769da2409806" + integrity sha512-1E2BdVVXQ4wDQ7f3mXCvS9xmfTVwEoT3oqKhjAr1iNlTJpBq10Z0VNBZd3VZ3nmCTFwTuUvs735QGnRE1gQ1BA== dependencies: bluebird "3.7.1" debug "4.1.1" + lodash "4.17.15" "@cypress/xvfb@1.2.4": version "1.2.4" @@ -865,34 +874,6 @@ dependencies: any-observable "^0.3.0" -"@types/blob-util@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" - integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== - -"@types/bluebird@3.5.29": - version "3.5.29" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6" - integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw== - -"@types/chai-jquery@1.1.40": - version "1.1.40" - resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.40.tgz#445bedcbbb2ae4e3027f46fa2c1733c43481ffa1" - integrity sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ== - dependencies: - "@types/chai" "*" - "@types/jquery" "*" - -"@types/chai@*": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" - integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== - -"@types/chai@4.2.7": - version "4.2.7" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.7.tgz#1c8c25cbf6e59ffa7d6b9652c78e547d9a41692d" - integrity sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g== - "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -903,71 +884,22 @@ resolved "https://registry.yarnpkg.com/@types/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.14.1.tgz#9787f4e89553ebc6359ce157a26ad51ed14aa98b" integrity sha512-CpYsiQ49UrOmadhFg0G5RkokPUmGGctD01mOWjNxFxHw5VgIRv33L2RyFHL8klaAI4HaedGN3Tcj4HTQ65hn+A== -"@types/jquery@*": - version "3.3.38" - resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.38.tgz#6385f1e1b30bd2bff55ae8ee75ea42a999cc3608" - integrity sha512-nkDvmx7x/6kDM5guu/YpXkGZ/Xj/IwGiLDdKM99YA5Vag7pjGyTJ8BNUh/6hxEn/sEu5DKtyRgnONJ7EmOoKrA== - dependencies: - "@types/sizzle" "*" - -"@types/jquery@3.3.31": - version "3.3.31" - resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" - integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg== - dependencies: - "@types/sizzle" "*" - -"@types/lodash@4.14.149": - version "4.14.149" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" - integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== - -"@types/minimatch@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/mocha@5.2.7": - version "5.2.7" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" - integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== - -"@types/node@^14.0.1": - version "14.0.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.1.tgz#5d93e0a099cd0acd5ef3d5bde3c086e1f49ff68c" - integrity sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA== +"@types/node@^14.0.14": + version "14.0.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" + integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== "@types/retry@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== -"@types/sinon-chai@3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.3.tgz#afe392303dda95cc8069685d1e537ff434fa506e" - integrity sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ== - dependencies: - "@types/chai" "*" - "@types/sinon" "*" - -"@types/sinon@*": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.0.tgz#5b70a360f55645dd64f205defd2a31b749a59799" - integrity sha512-v2TkYHkts4VXshMkcmot/H+ERZ2SevKa10saGaJPGCJ8vh3lKrC4u663zYEeRZxep+VbG6YRDtQ6gVqw9dYzPA== - dependencies: - "@types/sinonjs__fake-timers" "*" - -"@types/sinon@7.5.1": - version "7.5.1" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.1.tgz#d27b81af0d1cfe1f9b24eebe7a24f74ae40f5b7c" - integrity sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ== - -"@types/sinonjs__fake-timers@*": +"@types/sinonjs__fake-timers@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== -"@types/sizzle@*", "@types/sizzle@2.3.2": +"@types/sizzle@2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== @@ -1262,10 +1194,10 @@ aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -arch@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" - integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== +arch@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" + integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== argparse@^1.0.7: version "1.0.10" @@ -1347,7 +1279,7 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async@^3.1.0: +async@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== @@ -2046,10 +1978,10 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" - integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== +commander@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@^2.19.0, commander@^2.20.0, commander@^2.9.0: version "2.20.3" @@ -2141,6 +2073,11 @@ core-js-compat@^3.1.1: browserslist "^4.8.3" semver "7.0.0" +core-js-pure@^3.0.0: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" + integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== + core-js@^2.4.0: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -2278,10 +2215,10 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress-cucumber-preprocessor@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-2.3.1.tgz#dc9dee8d59d3c787c5c70fc4271c32e95575b083" - integrity sha512-cKa7/VsOthzvdSQSdFiLwSWtBrtDE2q/qAPDL6NWOF4Tqm/AWvvOv18b9l9Z1t4SpphezR7RGnG1QIU45y9PPw== +cypress-cucumber-preprocessor@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-2.5.2.tgz#d544616ece1fb361867e904678d970fe82398b54" + integrity sha512-djQjXmRWUKlA15GxWGhkqaeu1PalWeNrRyxij74QJ2dEp/ozQg35NeVABeWQjgjY2xTE87X6k5iC4y+Sbohe3A== dependencies: "@cypress/browserify-preprocessor" "^2.1.1" chai "^4.1.2" @@ -2297,48 +2234,39 @@ cypress-cucumber-preprocessor@^2.3.1: minimist "^1.2.0" through "^2.3.8" -cypress@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.5.0.tgz#01940d085f6429cec3c87d290daa47bb976a7c7b" - integrity sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ== +cypress@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.9.0.tgz#c188a3864ddf841c0fdc81a9e4eff5cf539cd1c1" + integrity sha512-qGxT5E0j21FPryzhb0OBjCdhoR/n1jXtumpFFSBPYWsaZZhNaBvc3XlBUDEZKkkXPsqUFYiyhWdHN/zo0t5FcA== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/request" "2.88.5" "@cypress/xvfb" "1.2.4" - "@types/blob-util" "1.3.3" - "@types/bluebird" "3.5.29" - "@types/chai" "4.2.7" - "@types/chai-jquery" "1.1.40" - "@types/jquery" "3.3.31" - "@types/lodash" "4.14.149" - "@types/minimatch" "3.0.3" - "@types/mocha" "5.2.7" - "@types/sinon" "7.5.1" - "@types/sinon-chai" "3.2.3" + "@types/sinonjs__fake-timers" "6.0.1" "@types/sizzle" "2.3.2" - arch "2.1.1" + arch "2.1.2" bluebird "3.7.2" cachedir "2.3.0" chalk "2.4.2" check-more-types "2.24.0" cli-table3 "0.5.1" - commander "4.1.0" + commander "4.1.1" common-tags "1.8.0" debug "4.1.1" - eventemitter2 "4.1.2" + eventemitter2 "6.4.2" execa "1.0.0" executable "4.1.1" extract-zip "1.7.0" fs-extra "8.1.0" - getos "3.1.4" + getos "3.2.1" is-ci "2.0.0" - is-installed-globally "0.1.0" + is-installed-globally "0.3.2" lazy-ass "1.6.0" listr "0.14.3" lodash "4.17.15" log-symbols "3.0.0" minimist "1.2.5" - moment "2.24.0" + moment "2.26.0" ospath "1.2.2" pretty-bytes "5.3.0" ramda "0.26.1" @@ -2407,6 +2335,13 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decamelize@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-3.2.0.tgz#84b8e8f4f8c579f938e35e2cc7024907e0090851" + integrity sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw== + dependencies: + xregexp "^4.2.4" + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -2698,10 +2633,10 @@ esutils@^2.0.0, esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter2@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" - integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= +eventemitter2@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.2.tgz#f31f8b99d45245f0edbc5b00797830ff3b388970" + integrity sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw== events@^2.0.0: version "2.1.0" @@ -3040,12 +2975,12 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -getos@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.4.tgz#29cdf240ed10a70c049add7b6f8cb08c81876faf" - integrity sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw== +getos@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== dependencies: - async "^3.1.0" + async "^3.2.0" getpass@^0.1.1: version "0.1.7" @@ -3079,12 +3014,12 @@ glob@^7.0.0, glob@^7.1.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== dependencies: - ini "^1.3.4" + ini "^1.3.5" globals@^11.1.0: version "11.12.0" @@ -3261,7 +3196,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4: +ini@^1.3.4, ini@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -3424,13 +3359,13 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" -is-installed-globally@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= +is-installed-globally@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" + global-dirs "^2.0.1" + is-path-inside "^3.0.1" is-interactive@^1.0.0: version "1.0.0" @@ -3456,12 +3391,10 @@ is-observable@^1.1.0: dependencies: symbol-observable "^1.1.0" -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" +is-path-inside@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" + integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" @@ -4031,10 +3964,10 @@ module-deps@^6.0.0: through2 "^2.0.0" xtend "^4.0.0" -moment@2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +moment@2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a" + integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw== move-concurrently@^1.0.1: version "1.0.1" @@ -4319,10 +4252,10 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== +p-limit@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.1.tgz#584784ac0722d1aed09f19f90ed2999af6ce2839" + integrity sha512-mw/p92EyOzl2MhauKodw54Rx5ZK4624rNfgNaBguFZkHzyUG9WsDzFF5/yQVEJinbJDdP4jEfMN+uBquiGnaLg== dependencies: p-try "^2.0.0" @@ -4436,11 +4369,6 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -4711,6 +4639,11 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + regenerator-transform@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb" @@ -5503,10 +5436,10 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -ts-loader@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.4.tgz#5d9b95227de5afb91fdd9668f8920eb193cfe0cc" - integrity sha512-5du6OQHl+4ZjO4crEyoYUyWSrmmo7bAO+inkaILZ68mvahqrfoa4nn0DRmpQ4ruT4l+cuJCgF0xD7SBIyLeeow== +ts-loader@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.5.tgz#789338fb01cb5dc0a33c54e50558b34a73c9c4c5" + integrity sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -5561,10 +5494,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== +typescript@3.9.6: + version "3.9.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" + integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== umd@^3.0.0: version "3.0.3" @@ -5740,10 +5673,10 @@ vm-browserify@^1.0.0, vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -wait-on@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.0.0.tgz#72e554b338490bbc7131362755ca1af04f46d029" - integrity sha512-6v9lttmGGRT7Lr16E/0rISTBIV1DN72n9+77Bpt1iBfzmhBI+75RDlacFe0Q+JizkmwWXmgHUcFG5cgx3Bwqzw== +wait-on@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.0.1.tgz#7dadfe83c36fdf034de996a41aa094af5cf23077" + integrity sha512-TxzkYIfRWK1hLc9IlUh9bE1mrvIIM3ptPRKQ86Z8Qo0tBQLCHEvWzkRD1Ge4FWprKflHOnAtqIBH2nKmib/lrg== dependencies: "@hapi/joi" "^17.1.1" axios "^0.19.2" @@ -5858,6 +5791,13 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +xregexp@^4.2.4: + version "4.3.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" + integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g== + dependencies: + "@babel/runtime-corejs3" "^7.8.3" + xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -5878,21 +5818,21 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs-parser@^18.1.1: - version "18.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1" - integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^15.3.1: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== +yargs@^15.4.0: + version "15.4.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.0.tgz#53949fb768309bac1843de9b17b80051e9805ec2" + integrity sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw== dependencies: cliui "^6.0.0" - decamelize "^1.2.0" + decamelize "^3.2.0" find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" @@ -5901,7 +5841,7 @@ yargs@^15.3.1: string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^18.1.1" + yargs-parser "^18.1.2" yauzl@2.10.0, yauzl@^2.10.0: version "2.10.0" diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index 778b1f2ad2d91..f460ff6ff9bf2 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -81,37 +81,32 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) ### API integration tests Our tests are separated in two suites: one suite runs with a basic license, and the other -with a trial license (the equivalent of gold+). This requires separate test servers and test runs. +with a trial license (the equivalent of gold+). This requires separate test servers and test runners. -**Start server** - -Basic: +**Basic** ``` +# Start server node scripts/functional_tests_server --config x-pack/test/apm_api_integration/basic/config.ts -``` - -Trial: -``` -node scripts/functional_tests_server --config x-pack/test/apm_api_integration/trial/config.ts +# Run tests +node scripts/functional_test_runner --config x-pack/test/apm_api_integration/basic/config.ts ``` -**Run tests** +The API tests for "basic" are located in `x-pack/test/apm_api_integration/basic/tests`. -Basic: +**Trial** ``` -node scripts/functional_test_runner --config x-pack/test/apm_api_integration/basic/config.ts -``` - -Trial: +# Start server +node scripts/functional_tests_server --config x-pack/test/apm_api_integration/trial/config.ts -``` +# Run tests node scripts/functional_test_runner --config x-pack/test/apm_api_integration/trial/config.ts ``` -APM tests are located in `x-pack/test/apm_api_integration`. +The API tests for "trial" are located in `x-pack/test/apm_api_integration/trial/tests`. + For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) ### Linting diff --git a/x-pack/plugins/global_search/server/mocks.ts b/x-pack/plugins/global_search/server/mocks.ts index 8a189a5701708..e7c133edf95c8 100644 --- a/x-pack/plugins/global_search/server/mocks.ts +++ b/x-pack/plugins/global_search/server/mocks.ts @@ -11,6 +11,7 @@ import { RouteHandlerGlobalSearchContext, } from './types'; import { searchServiceMock } from './services/search_service.mock'; +import { contextMock } from './services/context.mock'; const createSetupMock = (): jest.Mocked => { const searchMock = searchServiceMock.createSetupContract(); @@ -29,17 +30,18 @@ const createStartMock = (): jest.Mocked => { }; const createRouteHandlerContextMock = (): jest.Mocked => { - const contextMock = { + const handlerContextMock = { find: jest.fn(), }; - contextMock.find.mockReturnValue(of([])); + handlerContextMock.find.mockReturnValue(of([])); - return contextMock; + return handlerContextMock; }; export const globalSearchPluginMock = { createSetupContract: createSetupMock, createStartContract: createStartMock, createRouteHandlerContext: createRouteHandlerContextMock, + createProviderContext: contextMock.create, }; diff --git a/x-pack/plugins/global_search/server/services/context.mock.ts b/x-pack/plugins/global_search/server/services/context.mock.ts new file mode 100644 index 0000000000000..50c6da109f8dd --- /dev/null +++ b/x-pack/plugins/global_search/server/services/context.mock.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + savedObjectsTypeRegistryMock, + savedObjectsClientMock, + elasticsearchServiceMock, + uiSettingsServiceMock, +} from '../../../../../src/core/server/mocks'; + +const createContextMock = () => { + return { + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + typeRegistry: savedObjectsTypeRegistryMock.create(), + }, + elasticsearch: { + legacy: { + client: elasticsearchServiceMock.createScopedClusterClient(), + }, + }, + uiSettings: { + client: uiSettingsServiceMock.createClient(), + }, + }, + }; +}; + +const createFactoryMock = () => () => () => createContextMock(); + +export const contextMock = { + create: createContextMock, + createFactory: createFactoryMock, +}; diff --git a/x-pack/plugins/global_search_providers/kibana.json b/x-pack/plugins/global_search_providers/kibana.json index 025ea2bceed2c..39eca87d0bf89 100644 --- a/x-pack/plugins/global_search_providers/kibana.json +++ b/x-pack/plugins/global_search_providers/kibana.json @@ -2,7 +2,7 @@ "id": "globalSearchProviders", "version": "8.0.0", "kibanaVersion": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": ["globalSearch"], "optionalPlugins": [], diff --git a/x-pack/plugins/global_search_providers/server/index.ts b/x-pack/plugins/global_search_providers/server/index.ts new file mode 100644 index 0000000000000..26e4142d4865a --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/index.ts @@ -0,0 +1,11 @@ +/* + * 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 { PluginInitializer } from 'src/core/server'; +import { GlobalSearchProvidersPlugin, GlobalSearchProvidersPluginSetupDeps } from './plugin'; + +export const plugin: PluginInitializer<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> = () => + new GlobalSearchProvidersPlugin(); diff --git a/x-pack/plugins/global_search_providers/server/plugin.test.ts b/x-pack/plugins/global_search_providers/server/plugin.test.ts new file mode 100644 index 0000000000000..c9b51619d1789 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/plugin.test.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 { coreMock } from '../../../../src/core/server/mocks'; +import { globalSearchPluginMock } from '../../global_search/server/mocks'; +import { GlobalSearchProvidersPlugin } from './plugin'; + +describe('GlobalSearchProvidersPlugin', () => { + let plugin: GlobalSearchProvidersPlugin; + let globalSearchSetup: ReturnType; + + beforeEach(() => { + plugin = new GlobalSearchProvidersPlugin(); + globalSearchSetup = globalSearchPluginMock.createSetupContract(); + }); + + describe('#setup', () => { + it('registers the `savedObjects` result provider', () => { + const coreSetup = coreMock.createSetup(); + plugin.setup(coreSetup, { globalSearch: globalSearchSetup }); + + expect(globalSearchSetup.registerResultProvider).toHaveBeenCalledTimes(1); + expect(globalSearchSetup.registerResultProvider).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'savedObjects', + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/global_search_providers/server/plugin.ts b/x-pack/plugins/global_search_providers/server/plugin.ts new file mode 100644 index 0000000000000..64e7802937d80 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/plugin.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, Plugin } from 'src/core/server'; +import { GlobalSearchPluginSetup } from '../../global_search/server'; +import { createSavedObjectsResultProvider } from './providers'; + +export interface GlobalSearchProvidersPluginSetupDeps { + globalSearch: GlobalSearchPluginSetup; +} + +export class GlobalSearchProvidersPlugin + implements Plugin<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> { + setup( + { getStartServices }: CoreSetup<{}, {}>, + { globalSearch }: GlobalSearchProvidersPluginSetupDeps + ) { + globalSearch.registerResultProvider(createSavedObjectsResultProvider()); + return {}; + } + + start() { + return {}; + } +} diff --git a/x-pack/plugins/global_search_providers/server/providers/index.ts b/x-pack/plugins/global_search_providers/server/providers/index.ts new file mode 100644 index 0000000000000..1670871f305d9 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/providers/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createSavedObjectsResultProvider } from './saved_objects'; diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/index.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/index.ts new file mode 100644 index 0000000000000..4a67fd8b3df18 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createSavedObjectsResultProvider } from './provider'; diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts new file mode 100644 index 0000000000000..0085331c5be5f --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts @@ -0,0 +1,208 @@ +/* + * 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 { SavedObjectsFindResult, SavedObjectsType, SavedObjectTypeRegistry } from 'src/core/server'; +import { mapToResult, mapToResults } from './map_object_to_result'; + +const createType = (props: Partial): SavedObjectsType => { + return { + name: 'type', + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, + ...props, + }; +}; + +const createObject = ( + props: Partial, + attributes: T +): SavedObjectsFindResult => { + return { + id: 'id', + type: 'dashboard', + references: [], + score: 100, + ...props, + attributes, + }; +}; + +describe('mapToResult', () => { + it('converts a savedObject to a result', () => { + const type = createType({ + name: 'dashboard', + management: { + defaultSearchField: 'title', + getInAppUrl: (obj) => ({ path: `/dashboard/${obj.id}`, uiCapabilitiesPath: '' }), + }, + }); + + const obj = createObject( + { + id: 'dash1', + type: 'dashboard', + score: 42, + }, + { + title: 'My dashboard', + } + ); + + expect(mapToResult(obj, type)).toEqual({ + id: 'dash1', + title: 'My dashboard', + type: 'dashboard', + url: '/dashboard/dash1', + score: 42, + }); + }); + + it('throws if the type do not have management information', () => { + const object = createObject( + { id: 'dash1', type: 'dashboard', score: 42 }, + { title: 'My dashboard' } + ); + + expect(() => { + mapToResult( + object, + createType({ + name: 'dashboard', + management: { + getInAppUrl: (obj) => ({ path: `/dashboard/${obj.id}`, uiCapabilitiesPath: '' }), + }, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Trying to map an object from a type without management metadata"` + ); + + expect(() => { + mapToResult( + object, + createType({ + name: 'dashboard', + management: { + defaultSearchField: 'title', + }, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Trying to map an object from a type without management metadata"` + ); + + expect(() => { + mapToResult( + object, + createType({ + name: 'dashboard', + management: undefined, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Trying to map an object from a type without management metadata"` + ); + }); +}); + +describe('mapToResults', () => { + let typeRegistry: SavedObjectTypeRegistry; + + beforeEach(() => { + typeRegistry = new SavedObjectTypeRegistry(); + }); + + it('converts savedObjects to results', () => { + typeRegistry.registerType( + createType({ + name: 'typeA', + management: { + defaultSearchField: 'title', + getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: '' }), + }, + }) + ); + typeRegistry.registerType( + createType({ + name: 'typeB', + management: { + defaultSearchField: 'description', + getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'foo' }), + }, + }) + ); + typeRegistry.registerType( + createType({ + name: 'typeC', + management: { + defaultSearchField: 'excerpt', + getInAppUrl: (obj) => ({ path: `/type-c/${obj.id}`, uiCapabilitiesPath: 'bar' }), + }, + }) + ); + + const results = [ + createObject( + { + id: 'resultA', + type: 'typeA', + score: 100, + }, + { + title: 'titleA', + field: 'noise', + } + ), + createObject( + { + id: 'resultC', + type: 'typeC', + score: 42, + }, + { + excerpt: 'titleC', + title: 'foo', + } + ), + createObject( + { + id: 'resultB', + type: 'typeB', + score: 69, + }, + { + description: 'titleB', + bar: 'baz', + } + ), + ]; + + expect(mapToResults(results, typeRegistry)).toEqual([ + { + id: 'resultA', + title: 'titleA', + type: 'typeA', + url: '/type-a/resultA', + score: 100, + }, + { + id: 'resultC', + title: 'titleC', + type: 'typeC', + url: '/type-c/resultC', + score: 42, + }, + { + id: 'resultB', + title: 'titleB', + type: 'typeB', + url: '/type-b/resultB', + score: 69, + }, + ]); + }); +}); diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts new file mode 100644 index 0000000000000..c93558b1a3cf4 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + SavedObjectsType, + ISavedObjectTypeRegistry, + SavedObjectsFindResult, +} from 'src/core/server'; +import { GlobalSearchProviderResult } from '../../../../global_search/server'; + +export const mapToResults = ( + objects: Array>, + registry: ISavedObjectTypeRegistry +): GlobalSearchProviderResult[] => { + return objects.map((obj) => mapToResult(obj, registry.getType(obj.type)!)); +}; + +export const mapToResult = ( + object: SavedObjectsFindResult, + type: SavedObjectsType +): GlobalSearchProviderResult => { + const { defaultSearchField, getInAppUrl } = type.management ?? {}; + if (defaultSearchField === undefined || getInAppUrl === undefined) { + throw new Error('Trying to map an object from a type without management metadata'); + } + return { + id: object.id, + // defaultSearchField is dynamic and not 'directly' bound to the generic type of the SavedObject + // so we are forced to cast the attributes to any to access the properties associated with it. + title: (object.attributes as any)[defaultSearchField], + type: object.type, + url: getInAppUrl(object).path, + score: object.score, + }; +}; diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts new file mode 100644 index 0000000000000..84e05c67c5f66 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts @@ -0,0 +1,166 @@ +/* + * 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 { EMPTY } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { + SavedObjectsFindResponse, + SavedObjectsFindResult, + SavedObjectsType, + SavedObjectTypeRegistry, +} from 'src/core/server'; +import { globalSearchPluginMock } from '../../../../global_search/server/mocks'; +import { + GlobalSearchResultProvider, + GlobalSearchProviderFindOptions, +} from '../../../../global_search/server'; +import { createSavedObjectsResultProvider } from './provider'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + +const createFindResponse = ( + results: SavedObjectsFindResult[] +): SavedObjectsFindResponse => ({ + saved_objects: results, + page: 1, + per_page: 20, + total: results.length, +}); + +const createType = (props: Partial): SavedObjectsType => { + return { + name: 'type', + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, + ...props, + management: { + defaultSearchField: 'field', + getInAppUrl: (obj) => ({ path: `/object/${obj.id}`, uiCapabilitiesPath: '' }), + ...props.management, + }, + }; +}; + +const createObject = ( + props: Partial, + attributes: T +): SavedObjectsFindResult => { + return { + id: 'id', + type: 'dashboard', + score: 100, + references: [], + ...props, + attributes, + }; +}; + +const defaultOption: GlobalSearchProviderFindOptions = { + preference: 'pref', + maxResults: 20, + aborted$: EMPTY, +}; + +describe('savedObjectsResultProvider', () => { + let provider: GlobalSearchResultProvider; + let registry: SavedObjectTypeRegistry; + let context: ReturnType; + + beforeEach(() => { + provider = createSavedObjectsResultProvider(); + registry = new SavedObjectTypeRegistry(); + + registry.registerType( + createType({ + name: 'typeA', + management: { + defaultSearchField: 'title', + getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: '' }), + }, + }) + ); + registry.registerType( + createType({ + name: 'typeB', + management: { + defaultSearchField: 'description', + getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'foo' }), + }, + }) + ); + + context = globalSearchPluginMock.createProviderContext(); + context.core.savedObjects.client.find.mockResolvedValue(createFindResponse([])); + context.core.savedObjects.typeRegistry = registry as any; + }); + + it('has the correct id', () => { + expect(provider.id).toBe('savedObjects'); + }); + + it('calls `savedObjectClient.find` with the correct parameters', () => { + provider.find('term', defaultOption, context); + + expect(context.core.savedObjects.client.find).toHaveBeenCalledTimes(1); + expect(context.core.savedObjects.client.find).toHaveBeenCalledWith({ + page: 1, + perPage: defaultOption.maxResults, + search: 'term', + preference: 'pref', + searchFields: ['title', 'description'], + type: ['typeA', 'typeB'], + }); + }); + + it('converts the saved objects to results', async () => { + context.core.savedObjects.client.find.mockResolvedValue( + createFindResponse([ + createObject({ id: 'resultA', type: 'typeA', score: 50 }, { title: 'titleA' }), + createObject({ id: 'resultB', type: 'typeB', score: 78 }, { description: 'titleB' }), + ]) + ); + + const results = await provider.find('term', defaultOption, context).toPromise(); + expect(results).toEqual([ + { + id: 'resultA', + title: 'titleA', + type: 'typeA', + url: '/type-a/resultA', + score: 50, + }, + { + id: 'resultB', + title: 'titleB', + type: 'typeB', + url: '/type-b/resultB', + score: 78, + }, + ]); + }); + + it('only emits results until `aborted$` emits', () => { + getTestScheduler().run(({ hot, expectObservable }) => { + // test scheduler doesnt play well with promises. need to workaround by passing + // an observable instead. Behavior with promise is asserted in previous tests of the suite + context.core.savedObjects.client.find.mockReturnValue( + hot('---a', { a: createFindResponse([]) }) as any + ); + + const resultObs = provider.find( + 'term', + { ...defaultOption, aborted$: hot('-(a|)', { a: undefined }) }, + context + ); + + expectObservable(resultObs).toBe('-|'); + }); + }); +}); diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.ts new file mode 100644 index 0000000000000..b423b19ebc672 --- /dev/null +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.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 { from } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; +import { GlobalSearchResultProvider } from '../../../../global_search/server'; +import { mapToResults } from './map_object_to_result'; + +export const createSavedObjectsResultProvider = (): GlobalSearchResultProvider => { + return { + id: 'savedObjects', + find: (term, { aborted$, maxResults, preference }, { core }) => { + const { typeRegistry, client } = core.savedObjects; + + const searchableTypes = typeRegistry + .getVisibleTypes() + .filter((type) => type.management?.defaultSearchField && type.management?.getInAppUrl); + const searchFields = uniq( + searchableTypes.map((type) => type.management!.defaultSearchField!) + ); + + const responsePromise = client.find({ + page: 1, + perPage: maxResults, + search: term, + preference, + searchFields, + type: searchableTypes.map((type) => type.name), + }); + + return from(responsePromise).pipe( + takeUntil(aborted$), + map((res) => mapToResults(res.saved_objects, typeRegistry)) + ); + }, + }; +}; + +const uniq = (values: T[]): T[] => [...new Set(values)]; diff --git a/x-pack/plugins/infra/common/log_analysis/log_analysis.ts b/x-pack/plugins/infra/common/log_analysis/log_analysis.ts index f0aa2067a24c2..b8fba7a14e243 100644 --- a/x-pack/plugins/infra/common/log_analysis/log_analysis.ts +++ b/x-pack/plugins/infra/common/log_analysis/log_analysis.ts @@ -35,7 +35,7 @@ export type SetupStatus = | { type: 'skipped'; newlyCreated?: boolean; - }; // setup is hidden + }; // setup is not necessary /** * Maps a job status to the possibility that results have already been produced diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx index 10205e9684ef2..a0046b630bfe1 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx @@ -252,7 +252,7 @@ const getSetupStatus = (everyJobStatus: Record(everyJobStatus).reduce((setupStatus, [, jobStatus]) => { if (jobStatus === 'missing') { return { type: 'required', reason: 'missing' }; - } else if (setupStatus.type === 'required') { + } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { return setupStatus; } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { return { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index 5d9adb8a4f6ec..26633cd190a07 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import { isSetupStatusWithResults } from '../../../../common/log_analysis'; +import React, { useEffect, useState, useCallback } from 'react'; +import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, @@ -21,6 +21,7 @@ import { useLogSourceContext } from '../../../containers/logs/log_source'; import { LogEntryCategoriesResultsContent } from './page_results_content'; import { LogEntryCategoriesSetupContent } from './page_setup_content'; import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module'; +import { LogEntryCategoriesSetupFlyout } from './setup_flyout'; export const LogEntryCategoriesPageContent = () => { const { @@ -37,7 +38,11 @@ export const LogEntryCategoriesPageContent = () => { hasLogAnalysisSetupCapabilities, } = useLogAnalysisCapabilitiesContext(); - const { fetchJobStatus, setupStatus } = useLogEntryCategoriesModuleContext(); + const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryCategoriesModuleContext(); + + const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); + const openFlyout = useCallback(() => setIsFlyoutOpen(true), []); + const closeFlyout = useCallback(() => setIsFlyoutOpen(false), []); useEffect(() => { if (hasLogAnalysisReadCapabilities) { @@ -45,6 +50,13 @@ export const LogEntryCategoriesPageContent = () => { } }, [fetchJobStatus, hasLogAnalysisReadCapabilities]); + // Open flyout if there are no ML jobs + useEffect(() => { + if (setupStatus.type === 'required' && setupStatus.reason === 'missing') { + openFlyout(); + } + }, [setupStatus, openFlyout]); + if (isLoading || isUninitialized) { return ; } else if (hasFailedLoadingSource) { @@ -63,11 +75,21 @@ export const LogEntryCategoriesPageContent = () => { ); } else if (setupStatus.type === 'unknown') { return ; - } else if (isSetupStatusWithResults(setupStatus)) { - return ; + } else if (isJobStatusWithResults(jobStatus['log-entry-categories-count'])) { + return ( + <> + + + + ); } else if (!hasLogAnalysisSetupCapabilities) { return ; } else { - return ; + return ( + <> + + + + ); } }; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index a00351551e2d7..b4c044fe1cfcb 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -24,7 +24,13 @@ import { const JOB_STATUS_POLLING_INTERVAL = 30000; -export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => { +interface LogEntryCategoriesResultsContentProps { + onOpenSetup: () => void; +} + +export const LogEntryCategoriesResultsContent: React.FunctionComponent = ({ + onOpenSetup, +}) => { useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' }); useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 }); @@ -123,12 +129,25 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => { [setAutoRefresh] ); + const viewSetupFlyoutForReconfiguration = useCallback(() => { + viewSetupForReconfiguration(); + onOpenSetup(); + }, [onOpenSetup, viewSetupForReconfiguration]); + + const viewSetupFlyoutForUpdate = useCallback(() => { + viewSetupForUpdate(); + onOpenSetup(); + }, [onOpenSetup, viewSetupForUpdate]); + const hasResults = useMemo(() => topLogEntryCategories.length > 0, [ topLogEntryCategories.length, ]); const isFirstUse = useMemo( - () => setupStatus.type === 'skipped' && !!setupStatus.newlyCreated && !hasResults, + () => + ((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) || + setupStatus.type === 'succeeded') && + !hasResults, [hasResults, setupStatus] ); @@ -184,8 +203,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => { hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} hasStoppedJobs={hasStoppedJobs} isFirstUse={isFirstUse} - onRecreateMlJobForReconfiguration={viewSetupForReconfiguration} - onRecreateMlJobForUpdate={viewSetupForUpdate} + onRecreateMlJobForReconfiguration={viewSetupFlyoutForReconfiguration} + onRecreateMlJobForUpdate={viewSetupFlyoutForUpdate} qualityWarnings={categoryQualityWarnings} /> @@ -197,7 +216,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => { isLoadingTopCategories={isLoadingTopLogEntryCategories} jobId={jobIds['log-entry-categories-count']} onChangeDatasetSelection={setCategoryQueryDatasets} - onRequestRecreateMlJob={viewSetupForReconfiguration} + onRequestRecreateMlJob={viewSetupFlyoutForReconfiguration} selectedDatasets={categoryQueryDatasets} sourceId={sourceId} timeRange={categoryQueryTimeRange.timeRange} diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx index 7ae38234ae221..8d5d8a42200e6 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx @@ -4,98 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer, EuiSteps, EuiText } from '@elastic/eui'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; +import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui'; -import { BetaBadge } from '../../../components/beta_badge'; import { - createInitialConfigurationStep, - createProcessStep, LogAnalysisSetupPage, LogAnalysisSetupPageContent, LogAnalysisSetupPageHeader, } from '../../../components/logging/log_analysis_setup'; import { useTrackPageview } from '../../../../../observability/public'; -import { useLogEntryCategoriesSetup } from './use_log_entry_categories_setup'; -export const LogEntryCategoriesSetupContent: React.FunctionComponent = () => { +interface LogEntryCategoriesSetupContentProps { + onOpenSetup: () => void; +} + +export const LogEntryCategoriesSetupContent: React.FunctionComponent = ({ + onOpenSetup, +}) => { useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup' }); useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup', delay: 15000 }); - const { - cleanUpAndSetUp, - endTime, - isValidating, - lastSetupErrorMessages, - setEndTime, - setStartTime, - setValidatedIndices, - setUp, - setupStatus, - startTime, - validatedIndices, - validationErrors, - viewResults, - } = useLogEntryCategoriesSetup(); - - const steps = useMemo( - () => [ - createInitialConfigurationStep({ - setStartTime, - setEndTime, - startTime, - endTime, - isValidating, - validatedIndices, - setupStatus, - setValidatedIndices, - validationErrors, - }), - createProcessStep({ - cleanUpAndSetUp, - errorMessages: lastSetupErrorMessages, - isConfigurationValid: validationErrors.length <= 0 && !isValidating, - setUp, - setupStatus, - viewResults, - }), - ], - [ - cleanUpAndSetUp, - endTime, - isValidating, - lastSetupErrorMessages, - setEndTime, - setStartTime, - setUp, - setValidatedIndices, - setupStatus, - startTime, - validatedIndices, - validationErrors, - viewResults, - ] - ); - return ( {' '} - + defaultMessage="Set up log category analysis" + /> - +

+ +

- + + +
); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/setup_flyout.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/setup_flyout.tsx new file mode 100644 index 0000000000000..ab5eae1ab3004 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/setup_flyout.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiText, + EuiSpacer, + EuiSteps, +} from '@elastic/eui'; + +import { + createInitialConfigurationStep, + createProcessStep, +} from '../../../components/logging/log_analysis_setup'; +import { useLogEntryCategoriesSetup } from './use_log_entry_categories_setup'; + +interface LogEntryCategoriesSetupFlyoutProps { + isOpen: boolean; + onClose: () => void; +} + +export const LogEntryCategoriesSetupFlyout: React.FC = ({ + isOpen, + onClose, +}) => { + const { + cleanUpAndSetUp, + endTime, + isValidating, + lastSetupErrorMessages, + setEndTime, + setStartTime, + setValidatedIndices, + setUp, + setupStatus, + startTime, + validatedIndices, + validationErrors, + viewResults, + } = useLogEntryCategoriesSetup(); + + const viewResultsAndClose = useCallback(() => { + viewResults(); + onClose(); + }, [viewResults, onClose]); + + const steps = useMemo( + () => [ + createInitialConfigurationStep({ + setStartTime, + setEndTime, + startTime, + endTime, + isValidating, + validatedIndices, + setupStatus, + setValidatedIndices, + validationErrors, + }), + createProcessStep({ + cleanUpAndSetUp, + errorMessages: lastSetupErrorMessages, + isConfigurationValid: validationErrors.length <= 0 && !isValidating, + setUp, + setupStatus, + viewResults: viewResultsAndClose, + }), + ], + [ + cleanUpAndSetUp, + endTime, + isValidating, + lastSetupErrorMessages, + setEndTime, + setStartTime, + setUp, + setValidatedIndices, + setupStatus, + startTime, + validatedIndices, + validationErrors, + viewResultsAndClose, + ] + ); + + if (!isOpen) { + return null; + } + return ( + + + +

+ +

+
+
+ + +

+ +

+
+ + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index 4ec05a9778512..012b694bdbd25 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import { isSetupStatusWithResults } from '../../../../common/log_analysis'; +import React, { useEffect, useState, useCallback } from 'react'; +import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, @@ -21,6 +21,7 @@ import { useLogSourceContext } from '../../../containers/logs/log_source'; import { LogEntryRateResultsContent } from './page_results_content'; import { LogEntryRateSetupContent } from './page_setup_content'; import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; +import { LogEntryRateSetupFlyout } from './setup_flyout'; export const LogEntryRatePageContent = () => { const { @@ -37,7 +38,11 @@ export const LogEntryRatePageContent = () => { hasLogAnalysisSetupCapabilities, } = useLogAnalysisCapabilitiesContext(); - const { fetchJobStatus, setupStatus } = useLogEntryRateModuleContext(); + const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryRateModuleContext(); + + const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); + const openFlyout = useCallback(() => setIsFlyoutOpen(true), []); + const closeFlyout = useCallback(() => setIsFlyoutOpen(false), []); useEffect(() => { if (hasLogAnalysisReadCapabilities) { @@ -45,6 +50,13 @@ export const LogEntryRatePageContent = () => { } }, [fetchJobStatus, hasLogAnalysisReadCapabilities]); + // Open flyout if there are no ML jobs + useEffect(() => { + if (setupStatus.type === 'required' && setupStatus.reason === 'missing') { + openFlyout(); + } + }, [setupStatus, openFlyout]); + if (isLoading || isUninitialized) { return ; } else if (hasFailedLoadingSource) { @@ -63,11 +75,21 @@ export const LogEntryRatePageContent = () => { ); } else if (setupStatus.type === 'unknown') { return ; - } else if (isSetupStatusWithResults(setupStatus)) { - return ; + } else if (isJobStatusWithResults(jobStatus['log-entry-rate'])) { + return ( + <> + + + + ); } else if (!hasLogAnalysisSetupCapabilities) { return ; } else { - return ; + return ( + <> + + + + ); } }; 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 3c8db3f8246c0..bf4dbcd87cc41 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 @@ -36,7 +36,13 @@ import { const JOB_STATUS_POLLING_INTERVAL = 30000; -export const LogEntryRateResultsContent: React.FunctionComponent = () => { +interface LogEntryRateResultsContentProps { + onOpenSetup: () => void; +} + +export const LogEntryRateResultsContent: React.FunctionComponent = ({ + onOpenSetup, +}) => { useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' }); useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 }); @@ -127,13 +133,26 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { [setAutoRefresh] ); + const viewSetupFlyoutForReconfiguration = useCallback(() => { + viewSetupForReconfiguration(); + onOpenSetup(); + }, [viewSetupForReconfiguration, onOpenSetup]); + + const viewSetupFlyoutForUpdate = useCallback(() => { + viewSetupForUpdate(); + onOpenSetup(); + }, [viewSetupForUpdate, onOpenSetup]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const hasResults = useMemo(() => (logEntryRate?.histogramBuckets?.length ?? 0) > 0, [ logEntryRate, ]); const isFirstUse = useMemo( - () => setupStatus.type === 'skipped' && !!setupStatus.newlyCreated && !hasResults, + () => + ((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) || + setupStatus.type === 'succeeded') && + !hasResults, [hasResults, setupStatus] ); @@ -209,8 +228,8 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} hasStoppedJobs={hasStoppedJobs} isFirstUse={isFirstUse} - onRecreateMlJobForReconfiguration={viewSetupForReconfiguration} - onRecreateMlJobForUpdate={viewSetupForUpdate} + onRecreateMlJobForReconfiguration={viewSetupFlyoutForReconfiguration} + onRecreateMlJobForUpdate={viewSetupFlyoutForUpdate} /> @@ -227,7 +246,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { { +interface LogEntryRateSetupContentProps { + onOpenSetup: () => void; +} + +export const LogEntryRateSetupContent: React.FunctionComponent = ({ + onOpenSetup, +}) => { useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_setup' }); useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_setup', delay: 15000 }); - const { - cleanUpAndSetUp, - endTime, - isValidating, - lastSetupErrorMessages, - setEndTime, - setStartTime, - setValidatedIndices, - setUp, - setupStatus, - startTime, - validatedIndices, - validationErrors, - viewResults, - } = useLogEntryRateSetup(); - - const steps = useMemo( - () => [ - createInitialConfigurationStep({ - setStartTime, - setEndTime, - startTime, - endTime, - isValidating, - validatedIndices, - setupStatus, - setValidatedIndices, - validationErrors, - }), - createProcessStep({ - cleanUpAndSetUp, - errorMessages: lastSetupErrorMessages, - isConfigurationValid: validationErrors.length <= 0 && !isValidating, - setUp, - setupStatus, - viewResults, - }), - ], - [ - cleanUpAndSetUp, - endTime, - isValidating, - lastSetupErrorMessages, - setEndTime, - setStartTime, - setUp, - setValidatedIndices, - setupStatus, - startTime, - validatedIndices, - validationErrors, - viewResults, - ] - ); - return ( {' '} - + id="xpack.infra.logs.logEntryRate.setupTitle" + defaultMessage="Set up log anomaly analysis" + /> - +

+ +

- + + +
); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/setup_flyout.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/setup_flyout.tsx new file mode 100644 index 0000000000000..0e9e34432f28b --- /dev/null +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/setup_flyout.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiText, + EuiSpacer, + EuiSteps, +} from '@elastic/eui'; + +import { + createInitialConfigurationStep, + createProcessStep, +} from '../../../components/logging/log_analysis_setup'; +import { useLogEntryRateSetup } from './use_log_entry_rate_setup'; + +interface LogEntryRateSetupFlyoutProps { + isOpen: boolean; + onClose: () => void; +} + +export const LogEntryRateSetupFlyout: React.FC = ({ + isOpen, + onClose, +}) => { + const { + cleanUpAndSetUp, + endTime, + isValidating, + lastSetupErrorMessages, + setEndTime, + setStartTime, + setValidatedIndices, + setUp, + setupStatus, + startTime, + validatedIndices, + validationErrors, + viewResults, + } = useLogEntryRateSetup(); + + const viewResultsAndClose = useCallback(() => { + viewResults(); + onClose(); + }, [viewResults, onClose]); + + const steps = useMemo( + () => [ + createInitialConfigurationStep({ + setStartTime, + setEndTime, + startTime, + endTime, + isValidating, + validatedIndices, + setupStatus, + setValidatedIndices, + validationErrors, + }), + createProcessStep({ + cleanUpAndSetUp, + errorMessages: lastSetupErrorMessages, + isConfigurationValid: validationErrors.length <= 0 && !isValidating, + setUp, + setupStatus, + viewResults: viewResultsAndClose, + }), + ], + [ + cleanUpAndSetUp, + endTime, + isValidating, + lastSetupErrorMessages, + setEndTime, + setStartTime, + setUp, + setValidatedIndices, + setupStatus, + startTime, + validatedIndices, + validationErrors, + viewResultsAndClose, + ] + ); + + if (!isOpen) { + return null; + } + return ( + + + +

+ +

+
+
+ + +

+ +

+
+ + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/rollup/server/collectors/register.ts b/x-pack/plugins/rollup/server/collectors/register.ts index 35c40e42efc19..aa06d3f696d00 100644 --- a/x-pack/plugins/rollup/server/collectors/register.ts +++ b/x-pack/plugins/rollup/server/collectors/register.ts @@ -137,27 +137,30 @@ async function fetchRollupVisualizations( let rollupVisualizationsFromSavedSearches = 0; visualizations.forEach((visualization: any) => { - const { - _source: { - visualization: { - savedSearchRefName, - kibanaSavedObjectMeta: { searchSourceJSON }, - }, - references = [] as any[], - }, - } = visualization; - - const searchSource = JSON.parse(searchSourceJSON); - - if (savedSearchRefName) { + const references: Array<{ name: string; id: string }> | undefined = get( + visualization, + '_source.references' + ); + const savedSearchRefName: string | undefined = get( + visualization, + '_source.visualization.savedSearchRefName' + ); + const searchSourceJSON: string | undefined = get( + visualization, + '_source.visualization.kibanaSavedObjectMeta.searchSourceJSON' + ); + + if (savedSearchRefName && references?.length) { // This visualization depends upon a saved search. - const savedSearch = references.find((ref: any) => ref.name === savedSearchRefName); - if (rollupSavedSearchesToFlagMap[savedSearch.id]) { + const savedSearch = references.find(({ name }) => name === savedSearchRefName); + if (savedSearch && rollupSavedSearchesToFlagMap[savedSearch.id]) { rollupVisualizations++; rollupVisualizationsFromSavedSearches++; } - } else { + } else if (searchSourceJSON) { // This visualization depends upon an index pattern. + const searchSource = JSON.parse(searchSourceJSON); + if (rollupIndexPatternToFlagMap[searchSource.index]) { rollupVisualizations++; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx index 6bc829f766e58..2a0922d614f1d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx @@ -846,4 +846,43 @@ describe('FeatureTable', () => { }, }); }); + + it('does not render features which lack privileges', () => { + const role = createRole([ + { + spaces: ['foo'], + base: [], + feature: {}, + }, + ]); + + const featureWithoutPrivileges = createFeature({ + id: 'no_privs', + name: 'No Privileges Feature', + privileges: null, + }); + + const { displayedPrivileges } = setup({ + role, + features: [...kibanaFeatures, featureWithoutPrivileges], + privilegeIndex: 0, + calculateDisplayedPrivileges: true, + canCustomizeSubFeaturePrivileges: false, + }); + + expect(displayedPrivileges).toEqual({ + excluded_from_base: { + primaryFeaturePrivilege: 'none', + }, + no_sub_features: { + primaryFeaturePrivilege: 'none', + }, + with_excluded_sub_features: { + primaryFeaturePrivilege: 'none', + }, + with_sub_features: { + primaryFeaturePrivilege: 'none', + }, + }); + }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index a371a9ec9ba1e..57e24f2838226 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -63,7 +63,9 @@ export class FeatureTable extends Component { public render() { const { role, kibanaPrivileges } = this.props; - const featurePrivileges = kibanaPrivileges.getSecuredFeatures(); + const featurePrivileges = kibanaPrivileges + .getSecuredFeatures() + .filter((feature) => feature.privileges != null || feature.reserved != null); const items: TableRow[] = featurePrivileges .sort((feature1, feature2) => { diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts index a1bedea9f7deb..45f55b34baf96 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts @@ -50,7 +50,7 @@ describe('usingPrivileges', () => { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: [], + app: ['fooApp'], navLinkId: 'foo', privileges: null, }), @@ -63,6 +63,7 @@ describe('usingPrivileges', () => { Object.freeze({ navLinks: { foo: true, + fooApp: true, bar: true, }, management: { @@ -85,6 +86,7 @@ describe('usingPrivileges', () => { expect(result).toEqual({ navLinks: { foo: false, + fooApp: false, bar: true, }, management: { diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index 183ad9169a123..a9b3fa54d3617 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -18,8 +18,12 @@ export function disableUICapabilitiesFactory( logger: Logger, authz: AuthorizationServiceSetup ) { + // nav links are sourced from two places: + // 1) The `navLinkId` property. This is deprecated and will be removed (https://github.com/elastic/kibana/issues/66217) + // 2) The apps property. The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship. + // This behavior is replacing the `navLinkId` property above. const featureNavLinkIds = features - .map((feature) => feature.navLinkId) + .flatMap((feature) => [feature.navLinkId, ...feature.app]) .filter((navLinkId) => navLinkId != null); const shouldDisableFeatureUICapability = ( diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/common.ts b/x-pack/plugins/security_solution/common/endpoint/schema/common.ts index 7f8c938d54feb..fdb2570314cd0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/common.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/common.ts @@ -6,6 +6,15 @@ import * as t from 'io-ts'; +export const compressionAlgorithm = t.keyof({ + none: null, + zlib: null, +}); + +export const encryptionAlgorithm = t.keyof({ + none: null, +}); + export const identifier = t.string; export const manifestVersion = t.string; @@ -15,8 +24,8 @@ export const manifestSchemaVersion = t.keyof({ }); export type ManifestSchemaVersion = t.TypeOf; +export const relativeUrl = t.string; + export const sha256 = t.string; export const size = t.number; - -export const url = t.string; diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/manifest.ts b/x-pack/plugins/security_solution/common/endpoint/schema/manifest.ts index 470e9b13ef78a..2f03895d91c74 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/manifest.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/manifest.ts @@ -5,13 +5,26 @@ */ import * as t from 'io-ts'; -import { identifier, manifestSchemaVersion, manifestVersion, sha256, size, url } from './common'; +import { + compressionAlgorithm, + encryptionAlgorithm, + identifier, + manifestSchemaVersion, + manifestVersion, + relativeUrl, + sha256, + size, +} from './common'; export const manifestEntrySchema = t.exact( t.type({ - url, - sha256, - size, + relative_url: relativeUrl, + precompress_sha256: sha256, + precompress_size: size, + postcompress_sha256: sha256, + postcompress_size: size, + compression_algorithm: compressionAlgorithm, + encryption_algorithm: encryptionAlgorithm, }) ); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index c8c18696359f7..fd52fb6734ef2 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -30,7 +30,8 @@ import { loginAndWaitForPage } from '../tasks/login'; import { ALERTS_URL } from '../urls/navigation'; -describe('Alerts', () => { +// Flaky: https://github.com/elastic/kibana/issues/70727 +describe.skip('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { esArchiverLoad('alerts'); diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx index b6db6eb93d77f..533a9d51a9bcd 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_histogram_panel/index.tsx @@ -11,6 +11,7 @@ import styled from 'styled-components'; import { isEmpty } from 'lodash/fp'; import uuid from 'uuid'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { LegendItem } from '../../../common/components/charts/draggable_legend_item'; @@ -28,7 +29,7 @@ import { alertsHistogramOptions } from './config'; import { formatAlertsData, getAlertsHistogramQuery, showInitialLoadingSpinner } from './helpers'; import { AlertsHistogram } from './alerts_histogram'; import * as i18n from './translations'; -import { RegisterQuery, AlertsHistogramOption, AlertsAggregation, AlertsTotal } from './types'; +import { AlertsHistogramOption, AlertsAggregation, AlertsTotal } from './types'; import { LinkButton } from '../../../common/components/links'; import { SecurityPageName } from '../../../app/types'; @@ -52,12 +53,11 @@ const ViewAlertsFlexItem = styled(EuiFlexItem)` margin-left: 24px; `; -interface AlertsHistogramPanelProps { +interface AlertsHistogramPanelProps + extends Pick { chartHeight?: number; defaultStackByOption?: AlertsHistogramOption; - deleteQuery?: ({ id }: { id: string }) => void; filters?: Filter[]; - from: number; headerChildren?: React.ReactNode; /** Override all defaults, and only display this field */ onlyField?: string; @@ -65,12 +65,11 @@ interface AlertsHistogramPanelProps { legendPosition?: Position; panelHeight?: number; signalIndexName: string | null; - setQuery: (params: RegisterQuery) => void; showLinkToAlerts?: boolean; showTotalAlertsCount?: boolean; stackByOptions?: AlertsHistogramOption[]; + timelineId?: string; title?: string; - to: number; updateDateRange: UpdateDateRange; } @@ -98,8 +97,9 @@ export const AlertsHistogramPanel = memo( showLinkToAlerts = false, showTotalAlertsCount = false, stackByOptions, - to, + timelineId, title = i18n.HISTOGRAM_HEADER, + to, updateDateRange, }) => { // create a unique, but stable (across re-renders) query id @@ -163,11 +163,12 @@ export const AlertsHistogramPanel = memo( `draggable-legend-item-${uuid.v4()}-${selectedStackByOption.value}-${bucket.key}` ), field: selectedStackByOption.value, + timelineId, value: bucket.key, })) : NO_LEGEND_DATA, // eslint-disable-next-line react-hooks/exhaustive-deps - [alertsData, selectedStackByOption.value] + [alertsData, selectedStackByOption.value, timelineId] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx index a3e76557a6ff5..78a18dc336e5b 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/detection_engine_header_page/index.tsx @@ -22,3 +22,5 @@ DetectionEngineHeaderPageComponent.defaultProps = { }; export const DetectionEngineHeaderPage = React.memo(DetectionEngineHeaderPageComponent); + +DetectionEngineHeaderPage.displayName = 'DetectionEngineHeaderPage'; diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx index d033bc25e9801..fa7c85c95d87b 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx @@ -17,6 +17,11 @@ import { useWithSource } from '../../../common/containers/source'; jest.mock('../../components/user_info'); jest.mock('../../../common/containers/source'); jest.mock('../../../common/components/link_to'); +jest.mock('../../../common/containers/use_global_time', () => ({ + useGlobalTime: jest + .fn() + .mockReturnValue({ from: 0, isInitializing: false, to: 0, setQuery: jest.fn() }), +})); jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx index dc0b22c82af3e..5c525a8553477 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx @@ -12,7 +12,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { SecurityPageName } from '../../../app/types'; import { TimelineId } from '../../../../common/types/timeline'; -import { GlobalTime } from '../../../common/containers/global_time'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useWithSource } from '../../../common/containers/source'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; @@ -44,6 +44,7 @@ export const DetectionEnginePageComponent: React.FC = ({ query, setAbsoluteRangeDatePicker, }) => { + const { to, from, deleteQuery, setQuery } = useGlobalTime(); const { loading, isSignalIndexExists, @@ -131,36 +132,28 @@ export const DetectionEnginePageComponent: React.FC = ({ - - {({ to, from, deleteQuery, setQuery }) => ( - <> - <> - - - - - - )} - + + + ) : ( diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx index 0acb18082379a..11099e8cfc755 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx @@ -18,6 +18,12 @@ import { useParams } from 'react-router-dom'; jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); jest.mock('../../../../../common/containers/source'); +jest.mock('../../../../../common/containers/use_global_time', () => ({ + useGlobalTime: jest + .fn() + .mockReturnValue({ from: 0, isInitializing: false, to: 0, setQuery: jest.fn() }), +})); + jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -50,6 +56,6 @@ describe('RuleDetailsPageComponent', () => { } ); - expect(wrapper.find('GlobalTime')).toHaveLength(1); + expect(wrapper.find('DetectionEngineHeaderPage')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx index 2ec603546983e..b937e95c0a57e 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx @@ -56,7 +56,7 @@ import { StepPanel } from '../../../../components/rules/step_panel'; import { getStepsData, redirectToDetections, userHasNoPermissions } from '../helpers'; import * as ruleI18n from '../translations'; import * as i18n from './translations'; -import { GlobalTime } from '../../../../../common/containers/global_time'; +import { useGlobalTime } from '../../../../../common/containers/use_global_time'; import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config'; import { inputsSelectors } from '../../../../../common/store/inputs'; import { State } from '../../../../../common/store'; @@ -103,6 +103,7 @@ export const RuleDetailsPageComponent: FC = ({ query, setAbsoluteRangeDatePicker, }) => { + const { to, from, deleteQuery, setQuery } = useGlobalTime(); const { loading, isSignalIndexExists, @@ -263,169 +264,164 @@ export const RuleDetailsPageComponent: FC = ({ {hasIndexWrite != null && !hasIndexWrite && } {userHasNoPermissions(canUserCRUD) && } {indicesExist ? ( - - {({ to, from, deleteQuery, setQuery }) => ( - - - - - - - - {detectionI18n.LAST_ALERT} - {': '} - {lastAlerts} - , - ] - : []), - , - ]} - title={title} - > - + + + + + + + + {detectionI18n.LAST_ALERT} + {': '} + {lastAlerts} + , + ] + : []), + , + ]} + title={title} + > + + + + + + + + + - - - + {ruleI18n.EDIT_RULE_SETTINGS} + - - - - - {ruleI18n.EDIT_RULE_SETTINGS} - - - - - - + - - {ruleError} - - - - + + + + {ruleError} + + + + + + + + + + + {defineRuleData != null && ( + + )} + - - - - - - {defineRuleData != null && ( - - )} - - - - - - {scheduleRuleData != null && ( - - )} - - - + + + + {scheduleRuleData != null && ( + + )} + + + + + {tabs} + + {ruleDetailTab === RuleDetailTabs.alerts && ( + <> + - {tabs} - - {ruleDetailTab === RuleDetailTabs.alerts && ( - <> - - - {ruleId != null && ( - - )} - - )} - {ruleDetailTab === RuleDetailTabs.exceptions && ( - )} - {ruleDetailTab === RuleDetailTabs.failures && } - - - )} - + + )} + {ruleDetailTab === RuleDetailTabs.exceptions && ( + + )} + {ruleDetailTab === RuleDetailTabs.failures && } + + ) : ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx index 42fc2ac9b8453..fba8c3faa9237 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx @@ -117,6 +117,7 @@ interface BarChartComponentProps { barChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; stackByField?: string; + timelineId?: string; } const NO_LEGEND_DATA: LegendItem[] = []; @@ -125,6 +126,7 @@ export const BarChartComponent: React.FC = ({ barChart, configs, stackByField, + timelineId, }) => { const { ref: measureRef, width, height } = useThrottledResizeObserver(); const legendItems: LegendItem[] = useMemo( @@ -135,11 +137,12 @@ export const BarChartComponent: React.FC = ({ dataProviderId: escapeDataProviderId( `draggable-legend-item-${uuid.v4()}-${stackByField}-${d.key}` ), + timelineId, field: stackByField, value: d.key, })) : NO_LEGEND_DATA, - [barChart, stackByField] + [barChart, stackByField, timelineId] ); const customHeight = get('customHeight', configs); diff --git a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx index cdda1733932d5..bb71e5e73475d 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.tsx @@ -21,13 +21,14 @@ export interface LegendItem { color?: string; dataProviderId: string; field: string; + timelineId?: string; value: string; } const DraggableLegendItemComponent: React.FC<{ legendItem: LegendItem; }> = ({ legendItem }) => { - const { color, dataProviderId, field, value } = legendItem; + const { color, dataProviderId, field, timelineId, value } = legendItem; return ( @@ -44,6 +45,7 @@ const DraggableLegendItemComponent: React.FC<{ data-test-subj={`legend-item-${dataProviderId}`} field={field} id={dataProviderId} + timelineId={timelineId} value={value} /> ) : ( diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 3edc1d0d84b69..74efe2d34fcca 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -18,11 +18,10 @@ import { IdToDataProvider } from '../../store/drag_and_drop/model'; import { State } from '../../store/types'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; import { reArrangeProviders } from '../../../timelines/components/timeline/data_providers/helpers'; -import { ACTIVE_TIMELINE_REDUX_ID } from '../top_n'; import { ADDED_TO_TIMELINE_MESSAGE } from '../../hooks/translations'; import { useAddToTimelineSensor } from '../../hooks/use_add_to_timeline'; import { displaySuccessToast, useStateToaster } from '../toasters'; - +import { TimelineId } from '../../../../common/types/timeline'; import { addFieldToTimelineColumns, addProviderToTimeline, @@ -35,7 +34,7 @@ import { userIsReArrangingProviders, } from './helpers'; -// @ts-ignore +// @ts-expect-error window['__react-beautiful-dnd-disable-dev-warnings'] = true; interface Props { @@ -67,7 +66,7 @@ const onDragEndHandler = ({ destination: result.destination, dispatch, source: result.source, - timelineId: ACTIVE_TIMELINE_REDUX_ID, + timelineId: TimelineId.active, }); } else if (providerWasDroppedOnTimeline(result)) { addProviderToTimeline({ @@ -76,7 +75,7 @@ const onDragEndHandler = ({ dispatch, onAddedToTimeline, result, - timelineId: ACTIVE_TIMELINE_REDUX_ID, + timelineId: TimelineId.active, }); } else if (fieldWasDroppedOnTimelineColumns(result)) { addFieldToTimelineColumns({ @@ -130,7 +129,6 @@ export const DragDropContextWrapperComponent = React.memo {children} @@ -152,7 +150,7 @@ const emptyActiveTimelineDataProviders: DataProvider[] = []; // stable reference const mapStateToProps = (state: State) => { const activeTimelineDataProviders = - timelineSelectors.getTimelineByIdSelector()(state, ACTIVE_TIMELINE_REDUX_ID)?.dataProviders ?? + timelineSelectors.getTimelineByIdSelector()(state, TimelineId.active)?.dataProviders ?? emptyActiveTimelineDataProviders; const dataProviders = dragAndDropSelectors.dataProvidersSelector(state) ?? emptyDataProviders; diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx index 22b95f0d0c0e9..e7594365e8103 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { Draggable, DraggableProvided, @@ -22,7 +22,7 @@ import { DataProvider } from '../../../timelines/components/timeline/data_provid import { TruncatableText } from '../truncatable_text'; import { WithHoverActions } from '../with_hover_actions'; -import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content'; +import { DraggableWrapperHoverContent, useGetTimelineId } from './draggable_wrapper_hover_content'; import { getDraggableId, getDroppableId } from './helpers'; import { ProviderContainer } from './provider_container'; @@ -76,6 +76,7 @@ interface Props { dataProvider: DataProvider; inline?: boolean; render: RenderFunctionProp; + timelineId?: string; truncate?: boolean; onFilterAdded?: () => void; } @@ -100,16 +101,31 @@ export const getStyle = ( }; export const DraggableWrapper = React.memo( - ({ dataProvider, onFilterAdded, render, truncate }) => { + ({ dataProvider, onFilterAdded, render, timelineId, truncate }) => { + const draggableRef = useRef(null); + const [closePopOverTrigger, setClosePopOverTrigger] = useState(false); const [showTopN, setShowTopN] = useState(false); - const toggleTopN = useCallback(() => { - setShowTopN(!showTopN); - }, [setShowTopN, showTopN]); - + const [goGetTimelineId, setGoGetTimelineId] = useState(false); + const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId); const [providerRegistered, setProviderRegistered] = useState(false); const dispatch = useDispatch(); + const handleClosePopOverTrigger = useCallback( + () => setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger), + [] + ); + + const toggleTopN = useCallback(() => { + setShowTopN((prevShowTopN) => { + const newShowTopN = !prevShowTopN; + if (newShowTopN === false) { + handleClosePopOverTrigger(); + } + return newShowTopN; + }); + }, [handleClosePopOverTrigger]); + const registerProvider = useCallback(() => { if (!providerRegistered) { dispatch(dragAndDropActions.registerProvider({ provider: dataProvider })); @@ -126,17 +142,19 @@ export const DraggableWrapper = React.memo( () => () => { unRegisterProvider(); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [unRegisterProvider] ); const hoverContent = useMemo( () => ( ( } /> ), - [dataProvider, onFilterAdded, showTopN, toggleTopN] + [ + dataProvider, + handleClosePopOverTrigger, + onFilterAdded, + showTopN, + timelineId, + timelineIdFind, + toggleTopN, + ] ); const renderContent = useCallback( @@ -184,7 +210,10 @@ export const DraggableWrapper = React.memo( { + provided.innerRef(e); + draggableRef.current = e; + }} data-test-subj="providerContainer" isDragging={snapshot.isDragging} registerProvider={registerProvider} @@ -214,7 +243,12 @@ export const DraggableWrapper = React.memo( ); return ( - + ); }, (prevProps, nextProps) => diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx index ee1dc73b27fe2..3507b0f8c447d 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx @@ -52,6 +52,7 @@ jest.mock('../../../timelines/components/manage_timeline', () => { return { ...original, useManageTimeline: () => ({ + getManageTimelineById: jest.fn().mockReturnValue({ indexToAdd: [] }), getTimelineFilterManager: mockGetTimelineFilterManager, isManagedTimeline: jest.fn().mockReturnValue(false), }), @@ -63,8 +64,10 @@ const timelineId = TimelineId.active; const field = 'process.name'; const value = 'nice'; const toggleTopN = jest.fn(); +const goGetTimelineId = jest.fn(); const defaultProps = { field, + goGetTimelineId, showTopN: false, timelineId, toggleTopN, @@ -130,6 +133,18 @@ describe('DraggableWrapperHoverContent', () => { wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists() ).toBe(false); }); + + test(`it should call goGetTimelineId when user is over the 'Filter ${hoverAction} value' button`, () => { + const wrapper = mount( + + + + ); + const button = wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first(); + button.simulate('mouseenter'); + expect(goGetTimelineId).toHaveBeenCalledWith(true); + }); + describe('when run in the context of a timeline', () => { let wrapper: ReactWrapper; let onFilterAdded: () => void; @@ -151,6 +166,7 @@ describe('DraggableWrapperHoverContent', () => { ); }); + test('when clicked, it adds a filter to the timeline when running in the context of a timeline', () => { wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click'); wrapper.update(); @@ -459,6 +475,24 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); }); + test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, () => { + const whitelistedField = 'signal.rule.name'; + const wrapper = mount( + + + + ); + const button = wrapper.find(`[data-test-subj="show-top-field"]`).first(); + button.simulate('mouseenter'); + expect(goGetTimelineId).toHaveBeenCalledWith(true); + }); + test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, async () => { const whitelistedField = 'signal.rule.name'; const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index 4efdea5eee43b..a951bfa98d64b 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; import { DraggableId } from 'react-beautiful-dnd'; import { getAllFieldsByName, useWithSource } from '../../containers/source'; @@ -19,20 +19,25 @@ import { allowTopN } from './helpers'; import * as i18n from './translations'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { TimelineId } from '../../../../common/types/timeline'; +import { SELECTOR_TIMELINE_BODY_CLASS_NAME } from '../../../timelines/components/timeline/styles'; interface Props { + closePopOver?: () => void; draggableId?: DraggableId; field: string; + goGetTimelineId?: (args: boolean) => void; onFilterAdded?: () => void; showTopN: boolean; - timelineId?: string; + timelineId?: string | null; toggleTopN: () => void; value?: string[] | string | null; } const DraggableWrapperHoverContentComponent: React.FC = ({ + closePopOver, draggableId, field, + goGetTimelineId, onFilterAdded, showTopN, timelineId, @@ -44,17 +49,37 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [ kibana.services.data.query.filterManager, ]); - const { getTimelineFilterManager } = useManageTimeline(); + const { getManageTimelineById, getTimelineFilterManager } = useManageTimeline(); const filterManager = useMemo( () => - timelineId === TimelineId.active || - (draggableId != null && draggableId?.includes(TimelineId.active)) + timelineId === TimelineId.active ? getTimelineFilterManager(TimelineId.active) : filterManagerBackup, - [draggableId, timelineId, getTimelineFilterManager, filterManagerBackup] + [timelineId, getTimelineFilterManager, filterManagerBackup] ); + // Regarding data from useManageTimeline: + // * `indexToAdd`, which enables the alerts index to be appended to + // the `indexPattern` returned by `useWithSource`, may only be populated when + // this component is rendered in the context of the active timeline. This + // behavior enables the 'All events' view by appending the alerts index + // to the index pattern. + const { indexToAdd } = useMemo( + () => + timelineId === TimelineId.active + ? getManageTimelineById(TimelineId.active) + : { indexToAdd: null }, + [getManageTimelineById, timelineId] + ); + + const handleStartDragToTimeline = useCallback(() => { + startDragToTimeline(); + if (closePopOver != null) { + closePopOver(); + } + }, [closePopOver, startDragToTimeline]); + const filterForValue = useCallback(() => { const filter = value?.length === 0 ? createFilter(field, undefined) : createFilter(field, value); @@ -62,13 +87,15 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ if (activeFilterManager != null) { activeFilterManager.addFilters(filter); - + if (closePopOver != null) { + closePopOver(); + } if (onFilterAdded != null) { onFilterAdded(); } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [field, value, filterManager, onFilterAdded]); + }, [closePopOver, field, value, filterManager, onFilterAdded]); const filterOutValue = useCallback(() => { const filter = @@ -78,14 +105,23 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ if (activeFilterManager != null) { activeFilterManager.addFilters(filter); + if (closePopOver != null) { + closePopOver(); + } if (onFilterAdded != null) { onFilterAdded(); } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [field, value, filterManager, onFilterAdded]); + }, [closePopOver, field, value, filterManager, onFilterAdded]); - const { browserFields } = useWithSource(); + const handleGoGetTimelineId = useCallback(() => { + if (goGetTimelineId != null && timelineId == null) { + goGetTimelineId(true); + } + }, [goGetTimelineId, timelineId]); + + const { browserFields, indexPattern } = useWithSource('default', indexToAdd); return ( <> @@ -97,6 +133,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ data-test-subj="filter-for-value" iconType="magnifyWithPlus" onClick={filterForValue} + onMouseEnter={handleGoGetTimelineId} /> )} @@ -109,6 +146,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ data-test-subj="filter-out-value" iconType="magnifyWithMinus" onClick={filterOutValue} + onMouseEnter={handleGoGetTimelineId} /> )} @@ -120,7 +158,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ color="text" data-test-subj="add-to-timeline" iconType="timeline" - onClick={startDragToTimeline} + onClick={handleStartDragToTimeline} /> )} @@ -139,6 +177,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ data-test-subj="show-top-field" iconType="visBarVertical" onClick={toggleTopN} + onMouseEnter={handleGoGetTimelineId} /> )} @@ -147,7 +186,10 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ @@ -172,3 +214,30 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ DraggableWrapperHoverContentComponent.displayName = 'DraggableWrapperHoverContentComponent'; export const DraggableWrapperHoverContent = React.memo(DraggableWrapperHoverContentComponent); + +export const useGetTimelineId = function ( + elem: React.MutableRefObject, + getTimelineId: boolean = false +) { + const [timelineId, setTimelineId] = useState(null); + + useEffect(() => { + let startElem: Element | (Node & ParentNode) | null = elem.current; + if (startElem != null && getTimelineId) { + for (; startElem && startElem !== document; startElem = startElem.parentNode) { + const myElem: Element = startElem as Element; + if ( + myElem != null && + myElem.classList != null && + myElem.classList.contains(SELECTOR_TIMELINE_BODY_CLASS_NAME) && + myElem.hasAttribute('data-timeline-id') + ) { + setTimelineId(myElem.getAttribute('data-timeline-id')); + break; + } + } + } + }, [elem, getTimelineId]); + + return timelineId; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx index fcf007a4cf1ba..62a07550650aa 100644 --- a/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx @@ -21,6 +21,7 @@ export interface DefaultDraggableType { name?: string | null; queryValue?: string | null; children?: React.ReactNode; + timelineId?: string; tooltipContent?: React.ReactNode; } @@ -83,7 +84,7 @@ Content.displayName = 'Content'; * @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data */ export const DefaultDraggable = React.memo( - ({ id, field, value, name, children, tooltipContent, queryValue }) => + ({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) => value != null ? ( ( ) } + timelineId={timelineId} /> ) : null ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx index e01ccf1e544bb..7b6e9fb21a3e3 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx @@ -147,7 +147,6 @@ export const getColumns = ({ data-test-subj="field-name" fieldId={field} onUpdateColumns={onUpdateColumns} - timelineId={contextId} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 3e196c4b7bad4..16fe2a6669ff0 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -31,7 +31,7 @@ import { GetTitle, GetSubTitle, } from '../../components/matrix_histogram/types'; -import { SetQuery } from '../../../hosts/pages/navigation/types'; +import { GlobalTimeArgs } from '../../containers/use_global_time'; import { QueryTemplateProps } from '../../containers/query_template'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { InputsModelId } from '../../store/inputs/constants'; @@ -48,11 +48,12 @@ export interface OwnProps extends QueryTemplateProps { legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; showSpacer?: boolean; - setQuery: SetQuery; + setQuery: GlobalTimeArgs['setQuery']; setAbsoluteRangeDatePickerTarget?: InputsModelId; showLegend?: boolean; stackByOptions: MatrixHistogramOption[]; subtitle?: string | GetSubTitle; + timelineId?: string; title: string | GetTitle; type: hostsModel.HostsType | networkModel.NetworkType; } @@ -94,6 +95,7 @@ export const MatrixHistogramComponent: React.FC< stackByOptions, startDate, subtitle, + timelineId, title, titleSize, dispatchSetAbsoluteRangeDatePicker, @@ -242,6 +244,7 @@ export const MatrixHistogramComponent: React.FC< barChart={barChartData} configs={barchartConfigs} stackByField={selectedStackByOption.value} + timelineId={timelineId} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index a9e6cdd19bb20..ff0816758cb0c 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -8,10 +8,10 @@ import { EuiTitleSize } from '@elastic/eui'; import { ScaleType, Position, TickFormatter } from '@elastic/charts'; import { ActionCreator } from 'redux'; import { ESQuery } from '../../../../common/typed_json'; -import { SetQuery } from '../../../hosts/pages/navigation/types'; import { InputsModelId } from '../../store/inputs/constants'; import { HistogramType } from '../../../graphql/types'; import { UpdateDateRange } from '../charts/common'; +import { GlobalTimeArgs } from '../../containers/use_global_time'; export type MatrixHistogramMappingTypes = Record< string, @@ -47,15 +47,15 @@ interface MatrixHistogramBasicProps { from: number; to: number; }>; - endDate: number; + endDate: GlobalTimeArgs['to']; headerChildren?: React.ReactNode; hideHistogramIfEmpty?: boolean; id: string; legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; panelHeight?: number; - setQuery: SetQuery; - startDate: number; + setQuery: GlobalTimeArgs['setQuery']; + startDate: GlobalTimeArgs['from']; stackByOptions: MatrixHistogramOption[]; subtitle?: string | GetSubTitle; title?: string | GetTitle; @@ -80,11 +80,12 @@ export interface MatrixHistogramQueryProps { } export interface MatrixHistogramProps extends MatrixHistogramBasicProps { + legendPosition?: Position; scaleType?: ScaleType; - yTickFormatter?: (value: number) => string; showLegend?: boolean; showSpacer?: boolean; - legendPosition?: Position; + timelineId?: string; + yTickFormatter?: (value: number) => string; } export interface HistogramBucket { diff --git a/x-pack/plugins/security_solution/public/common/components/page/manage_query.tsx b/x-pack/plugins/security_solution/public/common/components/page/manage_query.tsx index 9e78f704b0f05..02d9a62f2890e 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/manage_query.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/manage_query.tsx @@ -9,16 +9,14 @@ import { omit } from 'lodash/fp'; import React from 'react'; import { inputsModel } from '../../store'; -import { SetQuery } from '../../../hosts/pages/navigation/types'; +import { GlobalTimeArgs } from '../../containers/use_global_time'; -interface OwnProps { - deleteQuery?: ({ id }: { id: string }) => void; +interface OwnProps extends Pick { headerChildren?: React.ReactNode; id: string; legendPosition?: Position; loading: boolean; refetch: inputsModel.Refetch; - setQuery: SetQuery; inspect?: inputsModel.InspectQuery; } diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index 336f906b3bed0..503e9983692f1 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -15,17 +15,19 @@ import { SUB_PLUGINS_REDUCER, kibanaObservable, createSecuritySolutionStorageMock, + mockIndexPattern, } from '../../mock'; import { createKibanaCoreStartMock } from '../../mock/kibana_core'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; import { createStore, State } from '../../store'; import { Props } from './top_n'; -import { ACTIVE_TIMELINE_REDUX_ID, StatefulTopN } from '.'; +import { StatefulTopN } from '.'; import { ManageGlobalTimeline, timelineDefaults, } from '../../../timelines/components/manage_timeline'; +import { TimelineId } from '../../../../common/types/timeline'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -94,9 +96,9 @@ const state: State = { timeline: { ...mockGlobalState.timeline, timelineById: { - [ACTIVE_TIMELINE_REDUX_ID]: { + [TimelineId.active]: { ...mockGlobalState.timeline.timelineById.test, - id: ACTIVE_TIMELINE_REDUX_ID, + id: TimelineId.active, dataProviders: [ { id: @@ -189,6 +191,9 @@ describe('StatefulTopN', () => { { beforeEach(() => { filterManager = new FilterManager(mockUiSettingsForFilterManager); const manageTimelineForTesting = { - [ACTIVE_TIMELINE_REDUX_ID]: { + [TimelineId.active]: { ...timelineDefaults, - id: ACTIVE_TIMELINE_REDUX_ID, + id: TimelineId.active, filterManager, }, }; @@ -278,6 +283,9 @@ describe('StatefulTopN', () => { { const filterManager = new FilterManager(mockUiSettingsForFilterManager); const manageTimelineForTesting = { - [ACTIVE_TIMELINE_REDUX_ID]: { + [TimelineId.active]: { ...timelineDefaults, - id: ACTIVE_TIMELINE_REDUX_ID, + id: TimelineId.active, filterManager, documentType: 'alerts', }, @@ -356,6 +364,9 @@ describe('StatefulTopN', () => { { // filters that appear at the top of most views in the app, and all the // filters in the active timeline: const mapStateToProps = (state: State) => { - const activeTimeline: TimelineModel = - getTimeline(state, ACTIVE_TIMELINE_REDUX_ID) ?? timelineDefaults; + const activeTimeline: TimelineModel = getTimeline(state, TimelineId.active) ?? timelineDefaults; const activeTimelineFilters = activeTimeline.filters ?? EMPTY_FILTERS; const activeTimelineInput: inputsModel.InputsRange = getInputsTimeline(state); @@ -48,7 +49,7 @@ const makeMapStateToProps = () => { activeTimelineEventType: activeTimeline.eventType, activeTimelineFilters, activeTimelineFrom: activeTimelineInput.timerange.from, - activeTimelineKqlQueryExpression: getKqlQueryTimeline(state, ACTIVE_TIMELINE_REDUX_ID), + activeTimelineKqlQueryExpression: getKqlQueryTimeline(state, TimelineId.active), activeTimelineTo: activeTimelineInput.timerange.to, dataProviders: activeTimeline.dataProviders, globalQuery: getGlobalQuerySelector(state), @@ -64,9 +65,17 @@ const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRang const connector = connect(makeMapStateToProps, mapDispatchToProps); +// * `indexToAdd`, which enables the alerts index to be appended to +// the `indexPattern` returned by `useWithSource`, may only be populated when +// this component is rendered in the context of the active timeline. This +// behavior enables the 'All events' view by appending the alerts index +// to the index pattern. interface OwnProps { browserFields: BrowserFields; field: string; + indexPattern: IIndexPattern; + indexToAdd: string[] | null; + timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; value?: string[] | string | null; @@ -83,86 +92,67 @@ const StatefulTopNComponent: React.FC = ({ browserFields, dataProviders, field, + indexPattern, + indexToAdd, globalFilters = EMPTY_FILTERS, globalQuery = EMPTY_QUERY, kqlMode, onFilterAdded, setAbsoluteRangeDatePicker, + timelineId, toggleTopN, value, }) => { const kibana = useKibana(); - - // Regarding data from useTimelineTypeContext: - // * `documentType` (e.g. 'alerts') may only be populated in some views, - // e.g. the `Alerts` view on the `Detections` page. - // * `id` (`timelineId`) may only be populated when we are rendered in the - // context of the active timeline. - // * `indexToAdd`, which enables the alerts index to be appended to - // the `indexPattern` returned by `useWithSource`, may only be populated when - // this component is rendered in the context of the active timeline. This - // behavior enables the 'All events' view by appending the alerts index - // to the index pattern. - const { isManagedTimeline, getManageTimelineById } = useManageTimeline(); - const { documentType, id: timelineId, indexToAdd } = useMemo( - () => - isManagedTimeline(ACTIVE_TIMELINE_REDUX_ID) - ? getManageTimelineById(ACTIVE_TIMELINE_REDUX_ID) - : { documentType: null, id: null, indexToAdd: null }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [getManageTimelineById] - ); + const { from, deleteQuery, setQuery, to } = useGlobalTime(); const options = getOptions( - timelineId === ACTIVE_TIMELINE_REDUX_ID ? activeTimelineEventType : undefined + timelineId === TimelineId.active ? activeTimelineEventType : undefined ); - const { indexPattern } = useWithSource('default', indexToAdd); - return ( - - {({ from, deleteQuery, setQuery, to }) => ( - - )} - + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index 0ccb7e1e72f1f..5e2fd998224c6 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -5,14 +5,14 @@ */ import { EuiButtonIcon, EuiSuperSelect } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; +import { GlobalTimeArgs } from '../../containers/use_global_time'; import { EventsByDataset } from '../../../overview/components/events_by_dataset'; import { SignalsByCategory } from '../../../overview/components/signals_by_category'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; -import { inputsModel } from '../../store'; import { InputsModelId } from '../../store/inputs/constants'; import { EventType } from '../../../timelines/store/timeline/model'; @@ -43,13 +43,11 @@ const TopNContent = styled.div` } `; -export interface Props { +export interface Props extends Pick { combinedQueries?: string; defaultView: EventType; - deleteQuery?: ({ id }: { id: string }) => void; field: string; filters: Filter[]; - from: number; indexPattern: IIndexPattern; indexToAdd?: string[] | null; options: TopNOption[]; @@ -60,13 +58,7 @@ export interface Props { to: number; }>; setAbsoluteRangeDatePickerTarget: InputsModelId; - setQuery: (params: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; - to: number; + timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; value?: string[] | string | null; @@ -89,12 +81,17 @@ const TopNComponent: React.FC = ({ setAbsoluteRangeDatePicker, setAbsoluteRangeDatePickerTarget, setQuery, + timelineId, to, toggleTopN, }) => { const [view, setView] = useState(defaultView); const onViewSelected = useCallback((value: string) => setView(value as EventType), [setView]); + useEffect(() => { + setView(defaultView); + }, [defaultView]); + const headerChildren = useMemo( () => ( = ({ setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} setQuery={setQuery} showSpacer={false} + timelineId={timelineId} to={to} /> ) : ( @@ -145,6 +143,7 @@ const TopNComponent: React.FC = ({ setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} setQuery={setQuery} + timelineId={timelineId} to={to} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx index 8679dae448332..361779a4a33b2 100644 --- a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx @@ -5,7 +5,7 @@ */ import { EuiPopover } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { IS_DRAGGING_CLASS_NAME } from '../drag_and_drop/helpers'; @@ -22,6 +22,7 @@ interface Props { * Always show the hover menu contents (default: false) */ alwaysShow?: boolean; + closePopOverTrigger?: boolean; /** * The contents of the hover menu. It is highly recommended you wrap this * content in a `div` with `position: absolute` to prevent it from effecting @@ -47,7 +48,8 @@ interface Props { * provides a signal to the content that the user is in a hover state. */ export const WithHoverActions = React.memo( - ({ alwaysShow = false, hoverContent, render }) => { + ({ alwaysShow = false, closePopOverTrigger, hoverContent, render }) => { + const [isOpen, setIsOpen] = useState(hoverContent != null && alwaysShow); const [showHoverContent, setShowHoverContent] = useState(false); const onMouseEnter = useCallback(() => { // NOTE: the following read from the DOM is expensive, but not as @@ -64,10 +66,16 @@ export const WithHoverActions = React.memo( const content = useMemo(() => <>{render(showHoverContent)}, [render, showHoverContent]); - const isOpen = hoverContent != null && (showHoverContent || alwaysShow); + useEffect(() => { + setIsOpen(hoverContent != null && (showHoverContent || alwaysShow)); + }, [hoverContent, showHoverContent, alwaysShow]); - const popover = useMemo(() => { - return ( + useEffect(() => { + setShowHoverContent(false); + }, [closePopOverTrigger]); + + return ( +
( isOpen={isOpen} panelPaddingSize={!alwaysShow ? 's' : 'none'} > - {isOpen ? hoverContent : null} + {isOpen ? <>{hoverContent} : null} - ); - }, [content, onMouseLeave, isOpen, alwaysShow, hoverContent]); - - return ( -
- {popover}
); } diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts index a2009809a9916..d716df70246f7 100644 --- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts +++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts @@ -7,7 +7,7 @@ import { ESTermQuery } from '../../../../../common/typed_json'; import { NarrowDateRange } from '../../../components/ml/types'; import { UpdateDateRange } from '../../../components/charts/common'; -import { SetQuery } from '../../../../hosts/pages/navigation/types'; +import { GlobalTimeArgs } from '../../use_global_time'; import { FlowTarget } from '../../../../graphql/types'; import { HostsType } from '../../../../hosts/store/model'; import { NetworkType } from '../../../../network/store//model'; @@ -22,11 +22,11 @@ export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & { // eslint-disable-next-line @typescript-eslint/no-explicit-any AnomaliesTableComponent: React.NamedExoticComponent; deleteQuery?: ({ id }: { id: string }) => void; - endDate: number; + endDate: GlobalTimeArgs['to']; flowTarget?: FlowTarget; narrowDateRange: NarrowDateRange; - setQuery: SetQuery; - startDate: number; + setQuery: GlobalTimeArgs['setQuery']; + startDate: GlobalTimeArgs['from']; skip: boolean; updateDateRange?: UpdateDateRange; hideHistogramIfEmpty?: boolean; diff --git a/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx b/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx deleted file mode 100644 index 9c9778c7074ee..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback, useState, useEffect } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; - -import { inputsModel, inputsSelectors, State } from '../../store'; -import { inputsActions } from '../../store/actions'; - -interface SetQuery { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch | inputsModel.RefetchKql; -} - -export interface GlobalTimeArgs { - from: number; - to: number; - setQuery: ({ id, inspect, loading, refetch }: SetQuery) => void; - deleteQuery?: ({ id }: { id: string }) => void; - isInitializing: boolean; -} - -interface OwnProps { - children: (args: GlobalTimeArgs) => React.ReactNode; -} - -type GlobalTimeProps = OwnProps & PropsFromRedux; - -export const GlobalTimeComponent: React.FC = ({ - children, - deleteAllQuery, - deleteOneQuery, - from, - to, - setGlobalQuery, -}) => { - const [isInitializing, setIsInitializing] = useState(true); - - const setQuery = useCallback( - ({ id, inspect, loading, refetch }: SetQuery) => - setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }), - [setGlobalQuery] - ); - - const deleteQuery = useCallback( - ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }), - [deleteOneQuery] - ); - - useEffect(() => { - if (isInitializing) { - setIsInitializing(false); - } - return () => { - deleteAllQuery({ id: 'global' }); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - <> - {children({ - isInitializing, - from, - to, - setQuery, - deleteQuery, - })} - - ); -}; - -const mapStateToProps = (state: State) => { - const timerange: inputsModel.TimeRange = inputsSelectors.globalTimeRangeSelector(state); - return { - from: timerange.from, - to: timerange.to, - }; -}; - -const mapDispatchToProps = { - deleteAllQuery: inputsActions.deleteAllQuery, - deleteOneQuery: inputsActions.deleteOneQuery, - setGlobalQuery: inputsActions.setQuery, -}; - -export const connector = connect(mapStateToProps, mapDispatchToProps); - -type PropsFromRedux = ConnectedProps; - -export const GlobalTime = connector(React.memo(GlobalTimeComponent)); - -GlobalTime.displayName = 'GlobalTime'; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 9aa3b007511a1..4f42f20c45ae1 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -97,7 +97,7 @@ export const useWithSource = ( const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const defaultIndex = useMemo(() => { if (indexToAdd != null && !isEmpty(indexToAdd)) { - return [...(!onlyCheckIndexToAdd ? configIndex : []), ...indexToAdd]; + return onlyCheckIndexToAdd ? indexToAdd : [...configIndex, ...indexToAdd]; } return configIndex; }, [configIndex, indexToAdd, onlyCheckIndexToAdd]); @@ -135,41 +135,32 @@ export const useWithSource = ( }, }, }); - if (!isSubscribed) { - return setState((prevState) => ({ - ...prevState, + + if (isSubscribed) { + setState({ loading: false, - })); + indicesExist: indicesExistOrDataTemporarilyUnavailable( + get('data.source.status.indicesExist', result) + ), + browserFields: getBrowserFields( + defaultIndex.join(), + get('data.source.status.indexFields', result) + ), + indexPattern: getIndexFields( + defaultIndex.join(), + get('data.source.status.indexFields', result) + ), + errorMessage: null, + }); } - - setState({ - loading: false, - indicesExist: indicesExistOrDataTemporarilyUnavailable( - get('data.source.status.indicesExist', result) - ), - browserFields: getBrowserFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - indexPattern: getIndexFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - errorMessage: null, - }); } catch (error) { - if (!isSubscribed) { - return setState((prevState) => ({ + if (isSubscribed) { + setState((prevState) => ({ ...prevState, loading: false, + errorMessage: error.message, })); } - - setState((prevState) => ({ - ...prevState, - loading: false, - errorMessage: error.message, - })); } } diff --git a/x-pack/plugins/security_solution/public/common/containers/use_global_time/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/use_global_time/index.test.tsx new file mode 100644 index 0000000000000..9d5f1740b0276 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/use_global_time/index.test.tsx @@ -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 { act, renderHook } from '@testing-library/react-hooks'; + +import { useGlobalTime } from '.'; + +jest.mock('react-redux', () => { + const originalModule = jest.requireActual('react-redux'); + + return { + ...originalModule, + useDispatch: jest.fn().mockReturnValue(jest.fn()), + useSelector: jest.fn().mockReturnValue({ from: 0, to: 0 }), + }; +}); + +describe('useGlobalTime', () => { + test('returns memoized value', () => { + const { result, rerender } = renderHook(() => useGlobalTime()); + + const result1 = result.current; + act(() => rerender()); + const result2 = result.current; + + expect(result1).toBe(result2); + expect(result1.from).toBe(0); + expect(result1.to).toBe(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/containers/use_global_time/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_global_time/index.tsx new file mode 100644 index 0000000000000..b63616ecbcf56 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/use_global_time/index.tsx @@ -0,0 +1,54 @@ +/* + * 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 { useCallback, useState, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { inputsSelectors } from '../../store'; +import { inputsActions } from '../../store/actions'; +import { SetQuery, DeleteQuery } from './types'; + +export const useGlobalTime = () => { + const dispatch = useDispatch(); + const { from, to } = useSelector(inputsSelectors.globalTimeRangeSelector); + const [isInitializing, setIsInitializing] = useState(true); + + const setQuery = useCallback( + ({ id, inspect, loading, refetch }: SetQuery) => + dispatch(inputsActions.setQuery({ inputId: 'global', id, inspect, loading, refetch })), + [dispatch] + ); + + const deleteQuery = useCallback( + ({ id }: DeleteQuery) => dispatch(inputsActions.deleteOneQuery({ inputId: 'global', id })), + [dispatch] + ); + + useEffect(() => { + if (isInitializing) { + setIsInitializing(false); + } + return () => { + dispatch(inputsActions.deleteAllQuery({ id: 'global' })); + }; + }, [dispatch, isInitializing]); + + const memoizedReturn = useMemo( + () => ({ + isInitializing, + from, + to, + setQuery, + deleteQuery, + }), + [deleteQuery, from, isInitializing, setQuery, to] + ); + + return memoizedReturn; +}; + +export type GlobalTimeArgs = Omit, 'deleteQuery'> & + Partial, 'deleteQuery'>>; diff --git a/x-pack/plugins/security_solution/public/common/containers/use_global_time/types.ts b/x-pack/plugins/security_solution/public/common/containers/use_global_time/types.ts new file mode 100644 index 0000000000000..9903c29202b29 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/use_global_time/types.ts @@ -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 { inputsActions } from '../../store/actions'; + +export type SetQuery = Pick< + Parameters[0], + 'id' | 'inspect' | 'loading' | 'refetch' +>; + +export type DeleteQuery = Pick[0], 'id'>; diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.test.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.test.ts index 4e2b11b24e5a9..e84438581fcde 100644 --- a/x-pack/plugins/security_solution/public/common/lib/keury/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.test.ts @@ -26,33 +26,33 @@ describe('Kuery escape', () => { expect(escapeKuery(value)).to.be(expected); }); - it('should escape keywords', () => { + it('should NOT escape keywords', () => { const value = 'foo and bar or baz not qux'; - const expected = 'foo \\and bar \\or baz \\not qux'; + const expected = 'foo and bar or baz not qux'; expect(escapeKuery(value)).to.be(expected); }); - it('should escape keywords next to each other', () => { + it('should NOT escape keywords next to each other', () => { const value = 'foo and bar or not baz'; - const expected = 'foo \\and bar \\or \\not baz'; + const expected = 'foo and bar or not baz'; expect(escapeKuery(value)).to.be(expected); }); it('should not escape keywords without surrounding spaces', () => { const value = 'And this has keywords, or does it not?'; - const expected = 'And this has keywords, \\or does it not?'; + const expected = 'And this has keywords, or does it not?'; expect(escapeKuery(value)).to.be(expected); }); - it('should escape uppercase keywords', () => { + it('should NOT escape uppercase keywords', () => { const value = 'foo AND bar'; - const expected = 'foo \\AND bar'; + const expected = 'foo AND bar'; expect(escapeKuery(value)).to.be(expected); }); - it('should escape both keywords and special characters', () => { + it('should escape special characters and NOT keywords', () => { const value = 'Hello, "world", and to meet you!'; - const expected = 'Hello, \\"world\\", \\and to meet you!'; + const expected = 'Hello, \\"world\\", and to meet you!'; expect(escapeKuery(value)).to.be(expected); }); diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts index bd4d96a98c815..b06a6ec10f48e 100644 --- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts @@ -75,11 +75,12 @@ const escapeWhitespace = (val: string) => const escapeSpecialCharacters = (val: string) => val.replace(/["]/g, '\\$&'); // $& means the whole matched string // See the Keyword rule in kuery.peg -const escapeAndOr = (val: string) => val.replace(/(\s+)(and|or)(\s+)/gi, '$1\\$2$3'); +// I do not think that we need that anymore since we are doing a full match_phrase all the time now => return `"${escapeKuery(val)}"`; +// const escapeAndOr = (val: string) => val.replace(/(\s+)(and|or)(\s+)/gi, '$1\\$2$3'); -const escapeNot = (val: string) => val.replace(/not(\s+)/gi, '\\$&'); +// const escapeNot = (val: string) => val.replace(/not(\s+)/gi, '\\$&'); -export const escapeKuery = flow(escapeSpecialCharacters, escapeAndOr, escapeNot, escapeWhitespace); +export const escapeKuery = flow(escapeSpecialCharacters, escapeWhitespace); export const convertToBuildEsQuery = ({ config, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index e520facf285c2..cce48a1e605b2 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -21,6 +21,12 @@ jest.mock('../../../common/containers/source', () => ({ useWithSource: jest.fn().mockReturnValue({ indicesExist: true, indexPattern: mockIndexPattern }), })); +jest.mock('../../../common/containers/use_global_time', () => ({ + useGlobalTime: jest + .fn() + .mockReturnValue({ from: 0, isInitializing: false, to: 0, setQuery: jest.fn() }), +})); + // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../../common/components/search_bar', () => ({ diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index 505d0f37ca039..acde0cbe1d42b 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -12,6 +12,7 @@ import { scoreIntervalToDateTime } from '../../../common/components/ml/score/sco import { Anomaly } from '../../../common/components/ml/types'; import { HostsTableType } from '../../store/model'; import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; import { AnomaliesHostTable } from '../../../common/components/ml/tables/anomalies_host_table'; import { HostDetailsTabsProps } from './types'; @@ -28,17 +29,13 @@ import { export const HostDetailsTabs = React.memo( ({ pageFilters, - deleteQuery, filterQuery, - from, - isInitializing, detailName, setAbsoluteRangeDatePicker, - setQuery, - to, indexPattern, hostDetailsPagePath, }) => { + const { from, to, isInitializing, deleteQuery, setQuery } = useGlobalTime(); const narrowDateRange = useCallback( (score: Anomaly, interval: string) => { const fromTo = scoreIntervalToDateTime(score, interval); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 1c66a9edc1947..46823f037b61c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -27,6 +27,7 @@ import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { HostOverviewByNameQuery } from '../../containers/hosts/overview'; import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useWithSource } from '../../../common/containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; import { useKibana } from '../../../common/lib/kibana'; @@ -50,17 +51,13 @@ const KpiHostDetailsManage = manageQuery(KpiHostsComponent); const HostDetailsComponent = React.memo( ({ filters, - from, - isInitializing, query, setAbsoluteRangeDatePicker, setHostDetailsTablesActivePageToZero, - setQuery, - to, detailName, - deleteQuery, hostDetailsPagePath, }) => { + const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); useEffect(() => { setHostDetailsTablesActivePageToZero(); }, [setHostDetailsTablesActivePageToZero, detailName]); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts index aa6288d473c91..7a440964c31ea 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts @@ -32,7 +32,7 @@ interface HostDetailsComponentDispatchProps extends HostBodyComponentDispatchPro setHostDetailsTablesActivePageToZero: ActionCreator; } -export interface HostDetailsProps extends HostsQueryProps { +export interface HostDetailsProps { detailName: string; hostDetailsPagePath: string; } diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index 1ea3a3020a1d5..566f8f23efd39 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -59,15 +59,8 @@ const mockHistory = { listen: jest.fn(), }; -const to = new Date('2018-03-23T18:49:23.132Z').valueOf(); -const from = new Date('2018-03-24T03:33:52.253Z').valueOf(); - describe('Hosts - rendering', () => { const hostProps: HostsComponentProps = { - from, - to, - setQuery: jest.fn(), - isInitializing: false, hostsPagePath: '', }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index f5cc651a30443..90438aec7c27e 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -22,6 +22,7 @@ import { manageQuery } from '../../common/components/page/manage_query'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { KpiHostsQuery } from '../containers/kpi_hosts'; +import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; import { LastEventIndexKey } from '../../graphql/types'; import { useKibana } from '../../common/lib/kibana'; @@ -44,17 +45,8 @@ import { HostsTableType } from '../store/model'; const KpiHostsComponentManage = manageQuery(KpiHostsComponent); export const HostsComponent = React.memo( - ({ - deleteQuery, - isInitializing, - filters, - from, - query, - setAbsoluteRangeDatePicker, - setQuery, - to, - hostsPagePath, - }) => { + ({ filters, query, setAbsoluteRangeDatePicker, hostsPagePath }) => { + const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); const capabilities = useMlCapabilities(); const kibana = useKibana(); const { tabName } = useParams(); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index c2285cf0a97e1..75cd36924dbba 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -11,7 +11,6 @@ import { HostDetails } from './details'; import { HostsTableType } from '../store/model'; import { MlHostConditionalContainer } from '../../common/components/ml/conditional_links/ml_host_conditional_container'; -import { GlobalTime } from '../../common/containers/global_time'; import { Hosts } from './hosts'; import { hostsPagePath, hostDetailsPagePath } from './types'; @@ -36,72 +35,49 @@ type Props = Partial> & { url: string }; export const HostsContainer = React.memo(({ url }) => { const history = useHistory(); + return ( - - {({ to, from, setQuery, deleteQuery, isInitializing }) => ( - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - { - history.replace(`${detailName}/${HostsTableType.authentications}${search}`); - return null; - }} - /> + + ( + + )} + /> + + + + } + /> + { + history.replace(`${detailName}/${HostsTableType.authentications}${search}`); + return null; + }} + /> - { - history.replace(`${HostsTableType.hosts}${search}`); - return null; - }} - /> - - )} - + { + history.replace(`${HostsTableType.hosts}${search}`); + return null; + }} + /> + ); }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts index 76f56fe1718aa..ddee940d11799 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts @@ -7,8 +7,7 @@ import { ESTermQuery } from '../../../../common/typed_json'; import { Filter, IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { NarrowDateRange } from '../../../common/components/ml/types'; -import { InspectQuery, Refetch } from '../../../common/store/inputs/model'; - +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { HostsTableType, HostsType } from '../../store/model'; import { NavTab } from '../../../common/components/navigation/types'; import { UpdateDateRange } from '../../../common/components/charts/common'; @@ -24,31 +23,19 @@ type KeyHostsNavTab = KeyHostsNavTabWithoutMlPermission | KeyHostsNavTabWithMlPe export type HostsNavTab = Record; -export type SetQuery = ({ - id, - inspect, - loading, - refetch, -}: { - id: string; - inspect: InspectQuery | null; - loading: boolean; - refetch: Refetch; -}) => void; - export interface QueryTabBodyProps { type: HostsType; - startDate: number; - endDate: number; + startDate: GlobalTimeArgs['from']; + endDate: GlobalTimeArgs['to']; filterQuery?: string | ESTermQuery; } export type HostsComponentsQueryProps = QueryTabBodyProps & { - deleteQuery?: ({ id }: { id: string }) => void; + deleteQuery?: GlobalTimeArgs['deleteQuery']; indexPattern: IIndexPattern; pageFilters?: Filter[]; skip: boolean; - setQuery: SetQuery; + setQuery: GlobalTimeArgs['setQuery']; updateDateRange?: UpdateDateRange; narrowDateRange?: NarrowDateRange; }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/types.ts index ffd17b0ef46f6..2c9ca4e4d27d9 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/types.ts @@ -8,23 +8,26 @@ import { IIndexPattern } from 'src/plugins/data/public'; import { ActionCreator } from 'typescript-fsa'; import { hostsModel } from '../store'; -import { GlobalTimeArgs } from '../../common/containers/global_time'; +import { GlobalTimeArgs } from '../../common/containers/use_global_time'; import { InputsModelId } from '../../common/store/inputs/constants'; export const hostsPagePath = '/'; export const hostDetailsPagePath = `/:detailName`; -export type HostsTabsProps = HostsComponentProps & { - filterQuery: string; - type: hostsModel.HostsType; - indexPattern: IIndexPattern; - setAbsoluteRangeDatePicker: ActionCreator<{ - id: InputsModelId; - from: number; - to: number; - }>; -}; +export type HostsTabsProps = HostsComponentProps & + GlobalTimeArgs & { + filterQuery: string; + type: hostsModel.HostsType; + indexPattern: IIndexPattern; + setAbsoluteRangeDatePicker: ActionCreator<{ + id: InputsModelId; + from: number; + to: number; + }>; + }; export type HostsQueryProps = GlobalTimeArgs; -export type HostsComponentProps = HostsQueryProps & { hostsPagePath: string }; +export interface HostsComponentProps { + hostsPagePath: string; +} diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx index 33eadad9aa774..76e197063fb8a 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { useIndexPatterns } from '../../../common/hooks/use_index_patterns'; import { EmbeddedMapComponent } from './embedded_map'; -import { SetQuery } from './types'; const mockUseIndexPatterns = useIndexPatterns as jest.Mock; jest.mock('../../../common/hooks/use_index_patterns'); @@ -18,7 +17,7 @@ mockUseIndexPatterns.mockImplementation(() => [true, []]); jest.mock('../../../common/lib/kibana'); describe('EmbeddedMapComponent', () => { - let setQuery: SetQuery; + let setQuery: jest.Mock; beforeEach(() => { setQuery = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx index 6470fc270d0bf..81aa4b1671fca 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx @@ -15,13 +15,13 @@ import { getIndexPatternTitleIdMapping } from '../../../common/hooks/api/helpers import { useIndexPatterns } from '../../../common/hooks/use_index_patterns'; import { Loader } from '../../../common/components/loader'; import { displayErrorToast, useStateToaster } from '../../../common/components/toasters'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { Embeddable } from './embeddable'; import { EmbeddableHeader } from './embeddable_header'; import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_helpers'; import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; -import { SetQuery } from './types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MapEmbeddable } from '../../../../../../plugins/maps/public/embeddable'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; @@ -73,7 +73,7 @@ export interface EmbeddedMapProps { filters: Filter[]; startDate: number; endDate: number; - setQuery: SetQuery; + setQuery: GlobalTimeArgs['setQuery']; } export const EmbeddedMapComponent = ({ diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx index b0f8e2cc02403..c58e53d07acba 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx @@ -8,7 +8,7 @@ import uuid from 'uuid'; import React from 'react'; import { OutPortal, PortalNode } from 'react-reverse-portal'; import minimatch from 'minimatch'; -import { IndexPatternMapping, SetQuery } from './types'; +import { IndexPatternMapping } from './types'; import { getLayerList } from './map_config'; import { MAP_SAVED_OBJECT_TYPE } from '../../../../../maps/public'; import { @@ -30,6 +30,7 @@ import { ErrorEmbeddable, } from '../../../../../../../src/plugins/embeddable/public'; import { IndexPatternSavedObject } from '../../../common/hooks/types'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; /** * Creates MapEmbeddable with provided initial configuration @@ -49,9 +50,9 @@ export const createEmbeddable = async ( filters: Filter[], indexPatterns: IndexPatternMapping[], query: Query, - startDate: number, - endDate: number, - setQuery: SetQuery, + startDate: GlobalTimeArgs['from'], + endDate: GlobalTimeArgs['to'], + setQuery: GlobalTimeArgs['setQuery'], portalNode: PortalNode, embeddableApi: EmbeddableStart ): Promise => { diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts index e3ca3c5b84289..700071f88a4b5 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/types.ts @@ -6,7 +6,6 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { RenderTooltipContentParams } from '../../../../../maps/public/classes/tooltips/tooltip_property'; -import { inputsModel } from '../../../common/store/inputs'; export interface IndexPatternMapping { title: string; @@ -29,12 +28,10 @@ export interface LayerMappingCollection { [indexPatternTitle: string]: LayerMapping; } -export type SetQuery = (params: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; -}) => void; +export interface MapFeature { + id: number; + layerId: string; +} export interface FeatureGeometry { coordinates: [number]; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx index 2bae19ce89aec..72e3161de5373 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx @@ -36,7 +36,7 @@ import { GetSubTitle, } from '../../../common/components/matrix_histogram/types'; import { UpdateDateRange } from '../../../common/components/charts/common'; -import { SetQuery } from '../../../hosts/pages/navigation/types'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { networkModel, networkSelectors } from '../../store'; const ID = 'networkDnsQuery'; @@ -67,7 +67,7 @@ interface DnsHistogramOwnProps extends QueryTemplatePaginatedProps { isDnsHistogram?: boolean; query: DocumentNode; scaleType: ScaleType; - setQuery: SetQuery; + setQuery: GlobalTimeArgs['setQuery']; showLegend?: boolean; stackByOptions: MatrixHistogramOption[]; subtitle?: string | GetSubTitle; diff --git a/x-pack/plugins/security_solution/public/network/pages/index.tsx b/x-pack/plugins/security_solution/public/network/pages/index.tsx index c7a8a5f705dfe..9ac05cc98bb45 100644 --- a/x-pack/plugins/security_solution/public/network/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/index.tsx @@ -13,7 +13,6 @@ import { FlowTarget } from '../../graphql/types'; import { IPDetails } from './ip_details'; import { Network } from './network'; -import { GlobalTime } from '../../common/containers/global_time'; import { getNetworkRoutePath } from './navigation'; import { NetworkRouteType } from './navigation/types'; import { MlNetworkConditionalContainer } from '../../common/components/ml/conditional_links/ml_network_conditional_container'; @@ -36,71 +35,48 @@ const NetworkContainerComponent: React.FC = () => { ); return ( - - {({ to, from, setQuery, deleteQuery, isInitializing }) => ( - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - { - history.replace(`ip/${detailName}/${FlowTarget.source}${search}`); - return null; - }} - /> - { - history.replace(`${NetworkRouteType.flows}${search}`); - return null; - }} - /> - - )} - + + ( + + )} + /> + + + + } + /> + { + history.replace(`ip/${detailName}/${FlowTarget.source}${search}`); + return null; + }} + /> + { + history.replace(`${NetworkRouteType.flows}${search}`); + return null; + }} + /> + ); }; diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx index 962a6269f8488..92f39228f07a7 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx @@ -33,6 +33,11 @@ type GlobalWithFetch = NodeJS.Global & { fetch: jest.Mock }; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/containers/source'); +jest.mock('../../../common/containers/use_global_time', () => ({ + useGlobalTime: jest + .fn() + .mockReturnValue({ from: 0, isInitializing: false, to: 0, setQuery: jest.fn() }), +})); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx index 162b3a7c158d5..6686b40e3c429 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { StickyContainer } from 'react-sticky'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; import { FiltersGlobal } from '../../../common/components/filters_global'; import { HeaderPage } from '../../../common/components/header_page'; import { LastEventTime } from '../../../common/components/last_event_time'; @@ -51,14 +52,11 @@ export const IPDetailsComponent: React.FC { + const { to, from, setQuery, isInitializing } = useGlobalTime(); const type = networkModel.NetworkType.details; const narrowDateRange = useCallback( (score, interval) => { diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/types.ts b/x-pack/plugins/security_solution/public/network/pages/ip_details/types.ts index 02d83208884b4..75fb5007f2701 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/types.ts @@ -8,16 +8,15 @@ import { IIndexPattern } from 'src/plugins/data/public'; import { ESTermQuery } from '../../../../common/typed_json'; import { NetworkType } from '../../store/model'; -import { InspectQuery, Refetch } from '../../../common/store/inputs/model'; import { FlowTarget, FlowTargetSourceDest } from '../../../graphql/types'; -import { GlobalTimeArgs } from '../../../common/containers/global_time'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; export const type = NetworkType.details; -export type IPDetailsComponentProps = GlobalTimeArgs & { +export interface IPDetailsComponentProps { detailName: string; flowTarget: FlowTarget; -}; +} export interface OwnProps { type: NetworkType; @@ -26,17 +25,7 @@ export interface OwnProps { filterQuery: string | ESTermQuery; ip: string; skip: boolean; - setQuery: ({ - id, - inspect, - loading, - refetch, - }: { - id: string; - inspect: InspectQuery | null; - loading: boolean; - refetch: Refetch; - }) => void; + setQuery: GlobalTimeArgs['setQuery']; } export type NetworkComponentsQueryProps = OwnProps & { diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts index 433ed7fffd741..6986d10ad3523 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts @@ -10,7 +10,7 @@ import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { NavTab } from '../../../common/components/navigation/types'; import { FlowTargetSourceDest } from '../../../graphql/types'; import { networkModel } from '../../store'; -import { GlobalTimeArgs } from '../../../common/containers/global_time'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { SetAbsoluteRangeDatePicker } from '../types'; import { NarrowDateRange } from '../../../common/components/ml/types'; diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index 4275c1641f517..bdaac1ac049e5 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -23,6 +23,7 @@ import { KpiNetworkComponent } from '..//components/kpi_network'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { KpiNetworkQuery } from '../../network/containers/kpi_network'; +import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; import { LastEventIndexKey } from '../../graphql/types'; import { useKibana } from '../../common/lib/kibana'; @@ -47,13 +48,10 @@ const NetworkComponent = React.memo( query, setAbsoluteRangeDatePicker, networkPagePath, - to, - from, - setQuery, - isInitializing, hasMlUserPermissions, capabilitiesFetched, }) => { + const { to, from, setQuery, isInitializing } = useGlobalTime(); const kibana = useKibana(); const { tabName } = useParams(); diff --git a/x-pack/plugins/security_solution/public/network/pages/types.ts b/x-pack/plugins/security_solution/public/network/pages/types.ts index e4170ee4b908b..54ff5a8d50b8e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/types.ts @@ -7,7 +7,6 @@ import { RouteComponentProps } from 'react-router-dom'; import { ActionCreator } from 'typescript-fsa'; import { InputsModelId } from '../../common/store/inputs/constants'; -import { GlobalTimeArgs } from '../../common/containers/global_time'; export type SetAbsoluteRangeDatePicker = ActionCreator<{ id: InputsModelId; @@ -15,9 +14,8 @@ export type SetAbsoluteRangeDatePicker = ActionCreator<{ to: number; }>; -export type NetworkComponentProps = Partial> & - GlobalTimeArgs & { - networkPagePath: string; - hasMlUserPermissions: boolean; - capabilitiesFetched: boolean; - }; +export type NetworkComponentProps = Partial> & { + networkPagePath: string; + hasMlUserPermissions: boolean; + capabilitiesFetched: boolean; +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index 03e8279f01db7..6e59d81a1eae9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -19,7 +19,6 @@ import { IIndexPattern, Query, } from '../../../../../../../src/plugins/data/public'; -import { inputsModel } from '../../../common/store'; import { HostsTableType, HostsType } from '../../../hosts/store/model'; import * as i18n from '../../pages/translations'; @@ -29,6 +28,7 @@ import { } from '../../../common/components/alerts_viewer/histogram_configs'; import { MatrixHisrogramConfigs } from '../../../common/components/matrix_histogram/types'; import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { SecurityPageName } from '../../../app/types'; import { useFormatUrl } from '../../../common/components/link_to'; import { LinkButton } from '../../../common/components/links'; @@ -39,20 +39,11 @@ const NO_FILTERS: Filter[] = []; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; const DEFAULT_STACK_BY = 'event.module'; -interface Props { - deleteQuery?: ({ id }: { id: string }) => void; +interface Props extends Pick { filters?: Filter[]; - from: number; hideHeaderChildren?: boolean; indexPattern: IIndexPattern; query?: Query; - setQuery: (params: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; - to: number; } const AlertsByCategoryComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx index 1773af86a382f..23f5998f44111 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx @@ -20,7 +20,7 @@ import { IIndexPattern, Query, } from '../../../../../../../src/plugins/data/public'; -import { inputsModel } from '../../../common/store'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; const HorizontalSpacer = styled(EuiFlexItem)` width: 24px; @@ -29,18 +29,10 @@ const HorizontalSpacer = styled(EuiFlexItem)` const NO_FILTERS: Filter[] = []; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; -interface Props { +interface Props extends Pick { filters?: Filter[]; - from: number; indexPattern: IIndexPattern; query?: Query; - setQuery: (params: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; - to: number; } const EventCountsComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index fe3f9f8ecda33..f18fccee50e22 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -27,9 +27,9 @@ import { IIndexPattern, Query, } from '../../../../../../../src/plugins/data/public'; -import { inputsModel } from '../../../common/store'; import { HostsTableType, HostsType } from '../../../hosts/store/model'; import { InputsModelId } from '../../../common/store/inputs/constants'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import * as i18n from '../../pages/translations'; import { SecurityPageName } from '../../../app/types'; @@ -42,25 +42,17 @@ const DEFAULT_STACK_BY = 'event.dataset'; const ID = 'eventsByDatasetOverview'; -interface Props { +interface Props extends Pick { combinedQueries?: string; - deleteQuery?: ({ id }: { id: string }) => void; filters?: Filter[]; - from: number; headerChildren?: React.ReactNode; indexPattern: IIndexPattern; indexToAdd?: string[] | null; onlyField?: string; query?: Query; setAbsoluteRangeDatePickerTarget?: InputsModelId; - setQuery: (params: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; showSpacer?: boolean; - to: number; + timelineId?: string; } const getHistogramOption = (fieldName: string): MatrixHistogramOption => ({ @@ -81,6 +73,7 @@ const EventsByDatasetComponent: React.FC = ({ setAbsoluteRangeDatePickerTarget, setQuery, showSpacer = true, + timelineId, to, }) => { // create a unique, but stable (across re-renders) query id @@ -177,6 +170,7 @@ const EventsByDatasetComponent: React.FC = ({ showSpacer={showSpacer} sourceId="default" startDate={from} + timelineId={timelineId} type={HostsType.page} {...eventsByDatasetHistogramConfigs} title={onlyField != null ? i18n.TOP(onlyField) : eventsByDatasetHistogramConfigs.title} diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx index 195bb4fa0807a..583c76d1464a8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx @@ -18,26 +18,16 @@ import { useUiSetting$, useKibana } from '../../../common/lib/kibana'; import { getHostsUrl, useFormatUrl } from '../../../common/components/link_to'; import { getOverviewHostStats, OverviewHostStats } from '../overview_host_stats'; import { manageQuery } from '../../../common/components/page/manage_query'; -import { inputsModel } from '../../../common/store/inputs'; import { InspectButtonContainer } from '../../../common/components/inspect'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { SecurityPageName } from '../../../app/types'; import { LinkButton } from '../../../common/components/links'; export interface OwnProps { - startDate: number; - endDate: number; + startDate: GlobalTimeArgs['from']; + endDate: GlobalTimeArgs['to']; filterQuery?: ESQuery | string; - setQuery: ({ - id, - inspect, - loading, - refetch, - }: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; + setQuery: GlobalTimeArgs['setQuery']; } const OverviewHostStatsManage = manageQuery(OverviewHostStats); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx index a3760863bcb62..8282eaeb63c28 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx @@ -19,28 +19,18 @@ import { ID as OverviewNetworkQueryId, OverviewNetworkQuery, } from '../../containers/overview_network'; -import { inputsModel } from '../../../common/store/inputs'; import { getOverviewNetworkStats, OverviewNetworkStats } from '../overview_network_stats'; import { getNetworkUrl, useFormatUrl } from '../../../common/components/link_to'; import { InspectButtonContainer } from '../../../common/components/inspect'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { SecurityPageName } from '../../../app/types'; import { LinkButton } from '../../../common/components/links'; export interface OverviewNetworkProps { - startDate: number; - endDate: number; + startDate: GlobalTimeArgs['from']; + endDate: GlobalTimeArgs['to']; filterQuery?: ESQuery | string; - setQuery: ({ - id, - inspect, - loading, - refetch, - }: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; + setQuery: GlobalTimeArgs['setQuery']; } const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats); diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx index 5010fd9c06eb7..8b62df60b257f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx @@ -11,19 +11,17 @@ import { alertsHistogramOptions } from '../../../alerts/components/alerts_histog import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { SetAbsoluteRangeDatePicker } from '../../../network/pages/types'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; -import { inputsModel } from '../../../common/store'; import { InputsModelId } from '../../../common/store/inputs/constants'; import * as i18n from '../../pages/translations'; import { UpdateDateRange } from '../../../common/components/charts/common'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; const DEFAULT_STACK_BY = 'signal.rule.threat.tactic.name'; const NO_FILTERS: Filter[] = []; -interface Props { - deleteQuery?: ({ id }: { id: string }) => void; +interface Props extends Pick { filters?: Filter[]; - from: number; headerChildren?: React.ReactNode; indexPattern: IIndexPattern; /** Override all defaults, and only display this field */ @@ -31,13 +29,7 @@ interface Props { query?: Query; setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker; setAbsoluteRangeDatePickerTarget?: InputsModelId; - setQuery: (params: { - id: string; - inspect: inputsModel.InspectQuery | null; - loading: boolean; - refetch: inputsModel.Refetch; - }) => void; - to: number; + timelineId?: string; } const SignalsByCategoryComponent: React.FC = ({ @@ -50,6 +42,7 @@ const SignalsByCategoryComponent: React.FC = ({ setAbsoluteRangeDatePicker, setAbsoluteRangeDatePickerTarget = 'global', setQuery, + timelineId, to, }) => { const { signalIndexName } = useSignalIndex(); @@ -83,6 +76,7 @@ const SignalsByCategoryComponent: React.FC = ({ showLinkToAlerts={onlyField == null ? true : false} stackByOptions={onlyField == null ? alertsHistogramOptions : undefined} legendPosition={'right'} + timelineId={timelineId} to={to} title={i18n.ALERT_COUNT} updateDateRange={updateDateRangeCallback} diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index bf5e7f0c211b1..9613a1e7210a3 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -19,6 +19,11 @@ import { Overview } from './index'; jest.mock('../../common/lib/kibana'); jest.mock('../../common/containers/source'); +jest.mock('../../common/containers/use_global_time', () => ({ + useGlobalTime: jest + .fn() + .mockReturnValue({ from: 0, isInitializing: false, to: 0, setQuery: jest.fn() }), +})); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index b8b8a67024c9f..2a522d3ea8fde 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import React, { useState, useMemo } from 'react'; +import React, { useCallback, useState, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { StickyContainer } from 'react-sticky'; import { Query, Filter } from 'src/plugins/data/public'; @@ -15,7 +15,7 @@ import { AlertsByCategory } from '../components/alerts_by_category'; import { FiltersGlobal } from '../../common/components/filters_global'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { GlobalTime } from '../../common/containers/global_time'; +import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; import { EventsByDataset } from '../components/events_by_dataset'; import { EventCounts } from '../components/event_counts'; @@ -46,6 +46,7 @@ const OverviewComponent: React.FC = ({ return [ENDPOINT_METADATA_INDEX]; }, []); + const { from, deleteQuery, setQuery, to } = useGlobalTime(); const { indicesExist, indexPattern } = useWithSource(); const { indicesExist: metadataIndexExists } = useWithSource( 'default', @@ -59,10 +60,10 @@ const OverviewComponent: React.FC = ({ ); const [dismissMessage, setDismissMessage] = useState(hasDismissEndpointNoticeMessage); - const dismissEndpointNotice = () => { + const dismissEndpointNotice = useCallback(() => { setDismissMessage(true); addMessage('management', 'dismissEndpointNotice'); - }; + }, [addMessage]); return ( <> @@ -85,59 +86,55 @@ const OverviewComponent: React.FC = ({ - - {({ from, deleteQuery, setQuery, to }) => ( - - - - - - - - - - - - - - - - - - - )} - + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx index aaad9cf145ab7..8922434746234 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx @@ -152,7 +152,6 @@ export const getFieldItems = ({ fieldId={field.name || ''} highlight={highlight} onUpdateColumns={onUpdateColumns} - timelineId={timelineId} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx index da0cbb99b8671..1f917c664e813 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx @@ -24,7 +24,6 @@ const defaultProps = { }), fieldId: timestampFieldId, onUpdateColumns: jest.fn(), - timelineId: 'timeline-id', }; describe('FieldName', () => { @@ -46,8 +45,7 @@ describe('FieldName', () => { ); - - wrapper.simulate('mouseenter'); + wrapper.find('div').at(1).simulate('mouseenter'); wrapper.update(); expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx index 985c8b35094ef..62e41d967cb9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx @@ -5,13 +5,16 @@ */ import { EuiHighlight, EuiText } from '@elastic/eui'; -import React, { useCallback, useState, useMemo } from 'react'; +import React, { useCallback, useState, useMemo, useRef } from 'react'; import styled from 'styled-components'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { OnUpdateColumns } from '../timeline/events'; import { WithHoverActions } from '../../../common/components/with_hover_actions'; -import { DraggableWrapperHoverContent } from '../../../common/components/drag_and_drop/draggable_wrapper_hover_content'; +import { + DraggableWrapperHoverContent, + useGetTimelineId, +} from '../../../common/components/drag_and_drop/draggable_wrapper_hover_content'; /** * The name of a (draggable) field @@ -77,23 +80,34 @@ export const FieldName = React.memo<{ fieldId: string; highlight?: string; onUpdateColumns: OnUpdateColumns; - timelineId: string; -}>(({ fieldId, highlight = '', timelineId }) => { +}>(({ fieldId, highlight = '' }) => { + const containerRef = useRef(null); + const [closePopOverTrigger, setClosePopOverTrigger] = useState(false); const [showTopN, setShowTopN] = useState(false); + const [goGetTimelineId, setGoGetTimelineId] = useState(false); + const timelineIdFind = useGetTimelineId(containerRef, goGetTimelineId); + const toggleTopN = useCallback(() => { - setShowTopN(!showTopN); - }, [setShowTopN, showTopN]); + setShowTopN((prevShowTopN) => !prevShowTopN); + }, []); + + const handleClosePopOverTrigger = useCallback( + () => setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger), + [] + ); const hoverContent = useMemo( () => ( ), - [fieldId, showTopN, toggleTopN, timelineId] + [fieldId, handleClosePopOverTrigger, showTopN, timelineIdFind, toggleTopN] ); const render = useCallback( @@ -109,7 +123,16 @@ export const FieldName = React.memo<{ [fieldId, highlight] ); - return ; + return ( +
+ +
+ ); }); FieldName.displayName = 'FieldName'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index be655f7465a1b..3b40c36fccd16 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -57,6 +57,11 @@ type ActionManageTimeline = id: string; payload: boolean; } + | { + type: 'SET_INDEX_TO_ADD'; + id: string; + payload: string[]; + } | { type: 'SET_TIMELINE_ACTIONS'; id: string; @@ -81,7 +86,10 @@ export const timelineDefaults = { title: i18n.EVENTS, unit: (n: number) => i18n.UNIT(n), }; -const reducerManageTimeline = (state: ManageTimelineById, action: ActionManageTimeline) => { +const reducerManageTimeline = ( + state: ManageTimelineById, + action: ActionManageTimeline +): ManageTimelineById => { switch (action.type) { case 'INITIALIZE_TIMELINE': return { @@ -91,7 +99,15 @@ const reducerManageTimeline = (state: ManageTimelineById, action: ActionManageTi ...state[action.id], ...action.payload, }, - }; + } as ManageTimelineById; + case 'SET_INDEX_TO_ADD': + return { + ...state, + [action.id]: { + ...state[action.id], + indexToAdd: action.payload, + }, + } as ManageTimelineById; case 'SET_TIMELINE_ACTIONS': case 'SET_TIMELINE_FILTER_MANAGER': return { @@ -100,7 +116,7 @@ const reducerManageTimeline = (state: ManageTimelineById, action: ActionManageTi ...state[action.id], ...action.payload, }, - }; + } as ManageTimelineById; case 'SET_IS_LOADING': return { ...state, @@ -108,7 +124,7 @@ const reducerManageTimeline = (state: ManageTimelineById, action: ActionManageTi ...state[action.id], isLoading: action.payload, }, - }; + } as ManageTimelineById; default: return state; } @@ -119,6 +135,7 @@ interface UseTimelineManager { getTimelineFilterManager: (id: string) => FilterManager | undefined; initializeTimeline: (newTimeline: ManageTimelineInit) => void; isManagedTimeline: (id: string) => boolean; + setIndexToAdd: (indexToAddArgs: { id: string; indexToAdd: string[] }) => void; setIsTimelineLoading: (isLoadingArgs: { id: string; isLoading: boolean }) => void; setTimelineRowActions: (actionsArgs: { id: string; @@ -129,10 +146,9 @@ interface UseTimelineManager { } const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseTimelineManager => { - const [state, dispatch] = useReducer( - reducerManageTimeline, - manageTimelineForTesting ?? initManageTimeline - ); + const [state, dispatch] = useReducer< + (state: ManageTimelineById, action: ActionManageTimeline) => ManageTimelineById + >(reducerManageTimeline, manageTimelineForTesting ?? initManageTimeline); const initializeTimeline = useCallback((newTimeline: ManageTimelineInit) => { dispatch({ @@ -183,8 +199,16 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT [] ); + const setIndexToAdd = useCallback(({ id, indexToAdd }: { id: string; indexToAdd: string[] }) => { + dispatch({ + type: 'SET_INDEX_TO_ADD', + id, + payload: indexToAdd, + }); + }, []); + const getTimelineFilterManager = useCallback( - (id: string): FilterManager | undefined => state[id].filterManager, + (id: string): FilterManager | undefined => state[id]?.filterManager, [state] ); const getManageTimelineById = useCallback( @@ -195,8 +219,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT initializeTimeline({ id }); return { ...timelineDefaults, id }; }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [state] + [initializeTimeline, state] ); const isManagedTimeline = useCallback((id: string): boolean => state[id] != null, [state]); @@ -205,6 +228,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT getTimelineFilterManager, initializeTimeline, isManagedTimeline, + setIndexToAdd, setIsTimelineLoading, setTimelineRowActions, setTimelineFilterManager, @@ -214,6 +238,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT const init = { getManageTimelineById: (id: string) => ({ ...timelineDefaults, id }), getTimelineFilterManager: () => undefined, + setIndexToAdd: () => undefined, isManagedTimeline: () => false, initializeTimeline: () => noop, setIsTimelineLoading: () => noop, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 51bf883ed2d61..43ea5e905ca8b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -17,6 +17,7 @@ import { columnRenderers, rowRenderers } from './renderers'; import { Sort } from './sort'; import { wait } from '../../../../common/lib/helpers'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; +import { SELECTOR_TIMELINE_BODY_CLASS_NAME } from '../styles'; const testBodyHeight = 700; const mockGetNotesByIds = (eventId: string[]) => []; @@ -133,6 +134,20 @@ describe('Body', () => { ).toEqual(true); }); }, 20000); + + test(`it add attribute data-timeline-id in ${SELECTOR_TIMELINE_BODY_CLASS_NAME}`, () => { + const wrapper = mount( + + + + ); + expect( + wrapper + .find(`[data-timeline-id="timeline-test"].${SELECTOR_TIMELINE_BODY_CLASS_NAME}`) + .first() + .exists() + ).toEqual(true); + }); }); describe('action on event', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 46895c86de084..6a296170fffde 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -139,6 +139,7 @@ export const Body = React.memo( )} ( pinnedEventIds={pinnedEventIds} rowRenderers={showRowRenderers ? rowRenderers : [plainRowRenderer]} selectedEventIds={selectedEventIds} - show={id === ACTIVE_TIMELINE_REDUX_ID ? show : true} + show={id === TimelineId.active ? show : true} showCheckboxes={showCheckboxes} sort={sort} toggleColumn={toggleColumn} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx index d1a58dafcb328..47d848021ba43 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx @@ -14,16 +14,17 @@ import { IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME } from '../../../common/component /** * TIMELINE BODY */ +export const SELECTOR_TIMELINE_BODY_CLASS_NAME = 'securitySolutionTimeline__body'; // SIDE EFFECT: the following creates a global class selector export const TimelineBodyGlobalStyle = createGlobalStyle` - body.${IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME} .siemTimeline__body { + body.${IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME} .${SELECTOR_TIMELINE_BODY_CLASS_NAME} { overflow: hidden; } `; export const TimelineBody = styled.div.attrs(({ className = '' }) => ({ - className: `siemTimeline__body ${className}`, + className: `${SELECTOR_TIMELINE_BODY_CLASS_NAME} ${className}`, }))<{ bodyHeight?: number; visible: boolean }>` height: ${({ bodyHeight }) => (bodyHeight ? `${bodyHeight}px` : 'auto')}; overflow: auto; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx index 07d4b004d2eda..18deaf0158723 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx @@ -174,6 +174,7 @@ export const TimelineComponent: React.FC = ({ const [isQueryLoading, setIsQueryLoading] = useState(false); const { initializeTimeline, + setIndexToAdd, setIsTimelineLoading, setTimelineFilterManager, setTimelineRowActions, @@ -188,12 +189,14 @@ export const TimelineComponent: React.FC = ({ }, []); useEffect(() => { setIsTimelineLoading({ id, isLoading: isQueryLoading || loadingIndexName }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loadingIndexName, isQueryLoading]); + }, [loadingIndexName, id, isQueryLoading, setIsTimelineLoading]); useEffect(() => { setTimelineFilterManager({ id, filterManager }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filterManager]); + }, [filterManager, id, setTimelineFilterManager]); + + useEffect(() => { + setIndexToAdd({ id, indexToAdd }); + }, [id, indexToAdd, setIndexToAdd]); return ( diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts index 67a331f4ba677..ace5aec77ed2c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts @@ -57,6 +57,8 @@ export const getPackageConfigCreateCallback = ( try { return updatedPackageConfig; } finally { + // TODO: confirm creation of package config + // then commit. await manifestManager.commit(wrappedManifest); } }; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts index 4c3153ca0ef11..b6a5bed9078ab 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts @@ -6,12 +6,12 @@ export const ArtifactConstants = { GLOBAL_ALLOWLIST_NAME: 'endpoint-exceptionlist', - SAVED_OBJECT_TYPE: 'endpoint:exceptions-artifact', + SAVED_OBJECT_TYPE: 'endpoint:user-artifact', SUPPORTED_OPERATING_SYSTEMS: ['linux', 'macos', 'windows'], SCHEMA_VERSION: '1.0.0', }; export const ManifestConstants = { - SAVED_OBJECT_TYPE: 'endpoint:exceptions-manifest', + SAVED_OBJECT_TYPE: 'endpoint:user-artifact-manifest', SCHEMA_VERSION: '1.0.0', }; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts index 7fd057afdbd55..2abb72234fecd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts @@ -7,22 +7,20 @@ import { createHash } from 'crypto'; import { validate } from '../../../../common/validate'; -import { - Entry, - EntryNested, - EntryMatch, - EntryMatchAny, -} from '../../../../../lists/common/schemas/types/entries'; +import { Entry, EntryNested } from '../../../../../lists/common/schemas/types/entries'; import { FoundExceptionListItemSchema } from '../../../../../lists/common/schemas/response/found_exception_list_item_schema'; import { ExceptionListClient } from '../../../../../lists/server'; import { InternalArtifactSchema, TranslatedEntry, - TranslatedEntryMatch, - TranslatedEntryMatchAny, - TranslatedEntryNested, WrappedTranslatedExceptionList, wrappedExceptionList, + TranslatedEntryNestedEntry, + translatedEntryNestedEntry, + translatedEntry as translatedEntryType, + TranslatedEntryMatcher, + translatedEntryMatchMatcher, + translatedEntryMatchAnyMatcher, } from '../../schemas'; import { ArtifactConstants } from './common'; @@ -36,11 +34,14 @@ export async function buildArtifact( return { identifier: `${ArtifactConstants.GLOBAL_ALLOWLIST_NAME}-${os}-${schemaVersion}`, - sha256, - encoding: 'application/json', + compressionAlgorithm: 'none', + encryptionAlgorithm: 'none', + decompressedSha256: sha256, + compressedSha256: sha256, + decompressedSize: exceptionsBuffer.byteLength, + compressedSize: exceptionsBuffer.byteLength, created: Date.now(), body: exceptionsBuffer.toString('base64'), - size: exceptionsBuffer.byteLength, }; } @@ -92,66 +93,80 @@ export function translateToEndpointExceptions( exc: FoundExceptionListItemSchema, schemaVersion: string ): TranslatedEntry[] { - const translatedList: TranslatedEntry[] = []; - if (schemaVersion === '1.0.0') { - exc.data.forEach((list) => { - list.entries.forEach((entry) => { - const tEntry = translateEntry(schemaVersion, entry); - if (tEntry !== undefined) { - translatedList.push(tEntry); + return exc.data + .flatMap((list) => { + return list.entries; + }) + .reduce((entries: TranslatedEntry[], entry) => { + const translatedEntry = translateEntry(schemaVersion, entry); + if (translatedEntry !== undefined && translatedEntryType.is(translatedEntry)) { + entries.push(translatedEntry); } - }); - }); + return entries; + }, []); } else { throw new Error('unsupported schemaVersion'); } - return translatedList; +} + +function getMatcherFunction(field: string, matchAny?: boolean): TranslatedEntryMatcher { + return matchAny + ? field.endsWith('.text') + ? 'exact_caseless_any' + : 'exact_cased_any' + : field.endsWith('.text') + ? 'exact_caseless' + : 'exact_cased'; +} + +function normalizeFieldName(field: string): string { + return field.endsWith('.text') ? field.substring(0, field.length - 5) : field; } function translateEntry( schemaVersion: string, entry: Entry | EntryNested ): TranslatedEntry | undefined { - let translatedEntry; switch (entry.type) { case 'nested': { - const e = (entry as unknown) as EntryNested; - const nestedEntries: TranslatedEntry[] = []; - for (const nestedEntry of e.entries) { - const translation = translateEntry(schemaVersion, nestedEntry); - if (translation !== undefined) { - nestedEntries.push(translation); - } - } - translatedEntry = { + const nestedEntries = entry.entries.reduce( + (entries: TranslatedEntryNestedEntry[], nestedEntry) => { + const translatedEntry = translateEntry(schemaVersion, nestedEntry); + if (nestedEntry !== undefined && translatedEntryNestedEntry.is(translatedEntry)) { + entries.push(translatedEntry); + } + return entries; + }, + [] + ); + return { entries: nestedEntries, - field: e.field, + field: entry.field, type: 'nested', - } as TranslatedEntryNested; - break; + }; } case 'match': { - const e = (entry as unknown) as EntryMatch; - translatedEntry = { - field: e.field.endsWith('.text') ? e.field.substring(0, e.field.length - 5) : e.field, - operator: e.operator, - type: e.field.endsWith('.text') ? 'exact_caseless' : 'exact_cased', - value: e.value, - } as TranslatedEntryMatch; - break; + const matcher = getMatcherFunction(entry.field); + return translatedEntryMatchMatcher.is(matcher) + ? { + field: normalizeFieldName(entry.field), + operator: entry.operator, + type: matcher, + value: entry.value, + } + : undefined; + } + case 'match_any': { + const matcher = getMatcherFunction(entry.field, true); + return translatedEntryMatchAnyMatcher.is(matcher) + ? { + field: normalizeFieldName(entry.field), + operator: entry.operator, + type: matcher, + value: entry.value, + } + : undefined; } - case 'match_any': - { - const e = (entry as unknown) as EntryMatchAny; - translatedEntry = { - field: e.field.endsWith('.text') ? e.field.substring(0, e.field.length - 5) : e.field, - operator: e.operator, - type: e.field.endsWith('.text') ? 'exact_caseless_any' : 'exact_cased_any', - value: e.value, - } as TranslatedEntryMatchAny; - } - break; } - return translatedEntry || undefined; } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts index 0434e3d8ffcb2..da8a449e1b026 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts @@ -55,21 +55,33 @@ describe('manifest', () => { expect(manifest1.toEndpointFormat()).toStrictEqual({ artifacts: { 'endpoint-exceptionlist-linux-1.0.0': { - sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', - size: 268, - url: + compression_algorithm: 'none', + encryption_algorithm: 'none', + precompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + postcompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + precompress_size: 268, + postcompress_size: 268, + relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-1.0.0/70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', }, 'endpoint-exceptionlist-macos-1.0.0': { - sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', - size: 268, - url: + compression_algorithm: 'none', + encryption_algorithm: 'none', + precompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + postcompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + precompress_size: 268, + postcompress_size: 268, + relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-macos-1.0.0/70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', }, 'endpoint-exceptionlist-windows-1.0.0': { - sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', - size: 268, - url: + compression_algorithm: 'none', + encryption_algorithm: 'none', + precompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + postcompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + precompress_size: 268, + postcompress_size: 268, + relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-1.0.0/70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', }, }, @@ -107,7 +119,7 @@ describe('manifest', () => { test('Manifest returns data for given artifact', async () => { const artifact = artifacts[0]; - const returned = manifest1.getArtifact(`${artifact.identifier}-${artifact.sha256}`); + const returned = manifest1.getArtifact(`${artifact.identifier}-${artifact.compressedSha256}`); expect(returned).toEqual(artifact); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.test.ts index 34bd2b0f388e1..c8cbdfc2fc5f4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.test.ts @@ -33,13 +33,17 @@ describe('manifest_entry', () => { }); test('Correct sha256 is returned', () => { - expect(manifestEntry.getSha256()).toEqual( + expect(manifestEntry.getCompressedSha256()).toEqual( + '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c' + ); + expect(manifestEntry.getDecompressedSha256()).toEqual( '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c' ); }); test('Correct size is returned', () => { - expect(manifestEntry.getSize()).toEqual(268); + expect(manifestEntry.getCompressedSize()).toEqual(268); + expect(manifestEntry.getDecompressedSize()).toEqual(268); }); test('Correct url is returned', () => { @@ -54,9 +58,13 @@ describe('manifest_entry', () => { test('Correct record is returned', () => { expect(manifestEntry.getRecord()).toEqual({ - sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', - size: 268, - url: + compression_algorithm: 'none', + encryption_algorithm: 'none', + precompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + postcompress_sha256: '70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', + precompress_size: 268, + postcompress_size: 268, + relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-1.0.0/70d2e0ee5db0073b242df9af32e64447b932b73c3e66de3a922c61a4077b1a9c', }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.ts index 00fd446bf14b5..860c2d7d704b2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest_entry.ts @@ -15,23 +15,31 @@ export class ManifestEntry { } public getDocId(): string { - return `${this.getIdentifier()}-${this.getSha256()}`; + return `${this.getIdentifier()}-${this.getCompressedSha256()}`; } public getIdentifier(): string { return this.artifact.identifier; } - public getSha256(): string { - return this.artifact.sha256; + public getCompressedSha256(): string { + return this.artifact.compressedSha256; } - public getSize(): number { - return this.artifact.size; + public getDecompressedSha256(): string { + return this.artifact.decompressedSha256; + } + + public getCompressedSize(): number { + return this.artifact.compressedSize; + } + + public getDecompressedSize(): number { + return this.artifact.decompressedSize; } public getUrl(): string { - return `/api/endpoint/artifacts/download/${this.getIdentifier()}/${this.getSha256()}`; + return `/api/endpoint/artifacts/download/${this.getIdentifier()}/${this.getCompressedSha256()}`; } public getArtifact(): InternalArtifactSchema { @@ -40,9 +48,13 @@ export class ManifestEntry { public getRecord(): ManifestEntrySchema { return { - sha256: this.getSha256(), - size: this.getSize(), - url: this.getUrl(), + compression_algorithm: 'none', + encryption_algorithm: 'none', + precompress_sha256: this.getDecompressedSha256(), + precompress_size: this.getDecompressedSize(), + postcompress_sha256: this.getCompressedSha256(), + postcompress_size: this.getCompressedSize(), + relative_url: this.getUrl(), }; } } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts index d38026fbcbbd9..5e61b278e87e4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts @@ -16,11 +16,27 @@ export const exceptionsArtifactSavedObjectMappings: SavedObjectsType['mappings'] identifier: { type: 'keyword', }, - sha256: { + compressionAlgorithm: { type: 'keyword', + index: false, + }, + encryptionAlgorithm: { + type: 'keyword', + index: false, }, - encoding: { + compressedSha256: { type: 'keyword', + }, + compressedSize: { + type: 'long', + index: false, + }, + decompressedSha256: { + type: 'keyword', + index: false, + }, + decompressedSize: { + type: 'long', index: false, }, created: { @@ -31,10 +47,6 @@ export const exceptionsArtifactSavedObjectMappings: SavedObjectsType['mappings'] type: 'binary', index: false, }, - size: { - type: 'long', - index: false, - }, }, }; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts index 08d02e70dac16..78b60e9e61f3e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts @@ -14,7 +14,7 @@ import { EndpointAppContext } from '../../types'; export const ManifestTaskConstants = { TIMEOUT: '1m', - TYPE: 'securitySolution:endpoint:exceptions-packager', + TYPE: 'endpoint:user-artifact-packager', VERSION: '1.0.0', }; diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts index 21d1105a313e7..d071896c537bf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts @@ -20,6 +20,9 @@ export const translatedEntryMatchAny = t.exact( ); export type TranslatedEntryMatchAny = t.TypeOf; +export const translatedEntryMatchAnyMatcher = translatedEntryMatchAny.type.props.type; +export type TranslatedEntryMatchAnyMatcher = t.TypeOf; + export const translatedEntryMatch = t.exact( t.type({ field: t.string, @@ -33,11 +36,23 @@ export const translatedEntryMatch = t.exact( ); export type TranslatedEntryMatch = t.TypeOf; +export const translatedEntryMatchMatcher = translatedEntryMatch.type.props.type; +export type TranslatedEntryMatchMatcher = t.TypeOf; + +export const translatedEntryMatcher = t.union([ + translatedEntryMatchMatcher, + translatedEntryMatchAnyMatcher, +]); +export type TranslatedEntryMatcher = t.TypeOf; + +export const translatedEntryNestedEntry = t.union([translatedEntryMatch, translatedEntryMatchAny]); +export type TranslatedEntryNestedEntry = t.TypeOf; + export const translatedEntryNested = t.exact( t.type({ field: t.string, type: t.keyof({ nested: null }), - entries: t.array(t.union([translatedEntryMatch, translatedEntryMatchAny])), + entries: t.array(translatedEntryNestedEntry), }) ); export type TranslatedEntryNested = t.TypeOf; diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.ts index 2e71ef98387f1..fe032586dda56 100644 --- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.ts @@ -5,17 +5,26 @@ */ import * as t from 'io-ts'; -import { identifier, sha256, size } from '../../../../common/endpoint/schema/common'; -import { body, created, encoding } from './common'; +import { + compressionAlgorithm, + encryptionAlgorithm, + identifier, + sha256, + size, +} from '../../../../common/endpoint/schema/common'; +import { body, created } from './common'; export const internalArtifactSchema = t.exact( t.type({ identifier, - sha256, - encoding, + compressionAlgorithm, + encryptionAlgorithm, + decompressedSha256: sha256, + decompressedSize: size, + compressedSha256: sha256, + compressedSize: size, created, body, - size, }) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts index 4a3dcaae1bd3d..00ae802ba6f32 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts @@ -16,7 +16,7 @@ export class ArtifactClient { } public getArtifactId(artifact: InternalArtifactSchema) { - return `${artifact.identifier}-${artifact.sha256}`; + return `${artifact.identifier}-${artifact.compressedSha256}`; } public async getArtifact(id: string): Promise> { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index bbb6fdfd50810..ef4f921cb537e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -38,9 +38,13 @@ describe('manifest_manager', () => { schema_version: '1.0.0', artifacts: { [artifact.identifier]: { - sha256: artifact.sha256, - size: artifact.size, - url: `/api/endpoint/artifacts/download/${artifact.identifier}/${artifact.sha256}`, + compression_algorithm: 'none', + encryption_algorithm: 'none', + precompress_sha256: artifact.decompressedSha256, + postcompress_sha256: artifact.compressedSha256, + precompress_size: artifact.decompressedSize, + postcompress_size: artifact.compressedSize, + relative_url: `/api/endpoint/artifacts/download/${artifact.identifier}/${artifact.compressedSha256}`, }, }, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 33b0d5db575c6..e47a23b893b71 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -180,17 +180,15 @@ export class ManifestManager { this.logger.info(`Dispatching new manifest with diffs: ${showDiffs(wrappedManifest.diffs)}`); let paging = true; + let page = 1; let success = true; while (paging) { - const { items, total, page } = await this.packageConfigService.list( - this.savedObjectsClient, - { - page: 1, - perPage: 20, - kuery: 'ingest-package-configs.package.name:endpoint', - } - ); + const { items, total } = await this.packageConfigService.list(this.savedObjectsClient, { + page, + perPage: 20, + kuery: 'ingest-package-configs.package.name:endpoint', + }); for (const packageConfig of items) { const { id, revision, updated_at, updated_by, ...newPackageConfig } = packageConfig; @@ -222,6 +220,7 @@ export class ManifestManager { } paging = page * items.length < total; + page++; } return success ? wrappedManifest : null; diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts index 1e01e04332f43..babd25dd3ec4b 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts @@ -43,7 +43,7 @@ const features = ([ id: 'feature_3', name: 'Feature 3', navLinkId: 'feature3', - app: [], + app: ['feature3_app'], catalogue: ['feature3Entry'], management: { kibana: ['indices'], @@ -67,6 +67,7 @@ const buildCapabilities = () => feature1: true, feature2: true, feature3: true, + feature3_app: true, unknownFeature: true, }, catalogue: { @@ -241,6 +242,7 @@ describe('capabilitiesSwitcher', () => { expectedCapabilities.feature_2.foo = false; expectedCapabilities.navLinks.feature3 = false; + expectedCapabilities.navLinks.feature3_app = false; expectedCapabilities.catalogue.feature3Entry = false; expectedCapabilities.management.kibana.indices = false; expectedCapabilities.feature_3.bar = false; diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts index a0cdd5ad0e931..05d0429596489 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts @@ -68,6 +68,12 @@ function toggleDisabledFeatures( navLinks[feature.navLinkId] = false; } + feature.app.forEach((app) => { + if (navLinks.hasOwnProperty(app)) { + navLinks[app] = false; + } + }); + // Disable associated catalogue entries const privilegeCatalogueEntries = feature.catalogue || []; privilegeCatalogueEntries.forEach((catalogueEntryId) => { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 683d83dde4e0f..51c6b33579f50 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7316,8 +7316,6 @@ "xpack.infra.alerting.logs.manageAlerts": "アラートを管理", "xpack.infra.alerting.manageAlerts": "アラートを管理", "xpack.infra.analysisSetup.actionStepTitle": "MLジョブを作成", - "xpack.infra.analysisSetup.analysisSetupDescription": "機械学習を使用して自動的に異常ログレートカウントを検出します。", - "xpack.infra.analysisSetup.analysisSetupTitle": "機械学習分析を有効にする", "xpack.infra.analysisSetup.configurationStepTitle": "構成", "xpack.infra.analysisSetup.createMlJobButton": "ML ジョブを作成", "xpack.infra.analysisSetup.deleteAnalysisResultsWarning": "これにより以前検出された異常が削除されます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ca065c9523637..8121df6d05090 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7320,8 +7320,6 @@ "xpack.infra.alerting.logs.manageAlerts": "管理告警", "xpack.infra.alerting.manageAlerts": "管理告警", "xpack.infra.analysisSetup.actionStepTitle": "创建 ML 作业", - "xpack.infra.analysisSetup.analysisSetupDescription": "使用 Machine Learning 自动检测异常日志速率计数。", - "xpack.infra.analysisSetup.analysisSetupTitle": "启用 Machine Learning 分析", "xpack.infra.analysisSetup.configurationStepTitle": "配置", "xpack.infra.analysisSetup.createMlJobButton": "创建 ML 作业", "xpack.infra.analysisSetup.deleteAnalysisResultsWarning": "这将移除以前检测到的异常。", diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap index 9496274a69171..823346db3518a 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap @@ -111,13 +111,13 @@ Array [ -

au-heartbeat

-
+
@@ -188,13 +188,13 @@ Array [ -

nyc-heartbeat

-
+
@@ -265,13 +265,13 @@ Array [ -

spa-heartbeat

-
+
@@ -356,18 +356,21 @@ exports[`AvailabilityReporting component shallow renders correctly against snaps "availability": 100, "color": "#d3dae6", "label": "au-heartbeat", + "status": "up", "timestamp": "36m ago", }, Object { "availability": 100, "color": "#d3dae6", "label": "nyc-heartbeat", + "status": "down", "timestamp": "36m ago", }, Object { "availability": 100, "color": "#d3dae6", "label": "spa-heartbeat", + "status": "down", "timestamp": "36m ago", }, ] diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap index 05e0b50a86f35..4d3e85ba18ebe 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap @@ -10,18 +10,21 @@ exports[`LocationStatusTags component renders properly against props 1`] = ` "availability": 100, "color": "#d3dae6", "label": "Berlin", + "status": "up", "timestamp": "1 Mon ago", }, Object { "availability": 100, "color": "#bd271e", "label": "Berlin", + "status": "down", "timestamp": "1 Mon ago", }, Object { "availability": 100, "color": "#d3dae6", "label": "Islamabad", + "status": "up", "timestamp": "1 Mon ago", }, ] @@ -145,13 +148,13 @@ exports[`LocationStatusTags component renders when all locations are down 1`] = -

Berlin

-
+
@@ -222,13 +225,13 @@ exports[`LocationStatusTags component renders when all locations are down 1`] = -

Islamabad

-
+
@@ -393,13 +396,13 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = ` -

Berlin

-
+
@@ -470,13 +473,13 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = ` -

Islamabad

-
+
@@ -641,13 +644,13 @@ exports[`LocationStatusTags component renders when there are many location 1`] = -

Berlin

-
+
@@ -718,13 +721,13 @@ exports[`LocationStatusTags component renders when there are many location 1`] = -

Islamabad

-
+
@@ -795,13 +798,13 @@ exports[`LocationStatusTags component renders when there are many location 1`] = -

New York

-
+
@@ -872,13 +875,13 @@ exports[`LocationStatusTags component renders when there are many location 1`] = -

Paris

-
+
@@ -949,13 +952,13 @@ exports[`LocationStatusTags component renders when there are many location 1`] = -

Sydney

-
+
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap index 3381efa62286b..28f1f433648c8 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap @@ -26,13 +26,13 @@ exports[`TagLabel component renders correctly against snapshot 1`] = ` -

US-East

-
+
@@ -44,13 +44,13 @@ exports[`TagLabel component shallow render correctly against snapshot 1`] = ` -

US-East

-
+
`; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx index de9f6b0d3b30f..b5fe5d17312c6 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx @@ -19,14 +19,22 @@ describe('AvailabilityReporting component', () => { timestamp: '36m ago', color: '#d3dae6', availability: 100, + status: 'up', }, { label: 'nyc-heartbeat', timestamp: '36m ago', color: '#d3dae6', availability: 100, + status: 'down', + }, + { + label: 'spa-heartbeat', + timestamp: '36m ago', + color: '#d3dae6', + availability: 100, + status: 'down', }, - { label: 'spa-heartbeat', timestamp: '36m ago', color: '#d3dae6', availability: 100 }, ]; }); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx index 3560784122298..8e46196ec3ab7 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx @@ -10,12 +10,12 @@ import { TagLabel } from '../tag_label'; describe('TagLabel component', () => { it('shallow render correctly against snapshot', () => { - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); it('renders correctly against snapshot', () => { - const component = renderWithIntl(); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx index 8fed5db5e0271..ccf7d41642bfb 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx @@ -30,7 +30,7 @@ export const AvailabilityReporting: React.FC = ({ allLocations }) => { name: LocationLabel, truncateText: true, render: (val: string, item: StatusTag) => { - return ; + return ; }, }, { diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx index 6096499213a10..b48252d4208d2 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx @@ -24,9 +24,10 @@ interface Props { export interface StatusTag { label: string; - timestamp: string; + timestamp?: string; color: string; - availability: number; + availability?: number; + status: 'up' | 'down'; } export const LocationStatusTags = ({ locations }: Props) => { @@ -48,6 +49,7 @@ export const LocationStatusTags = ({ locations }: Props) => { timestamp: moment(new Date(item.timestamp).valueOf()).fromNow(), color: item.summary.down === 0 ? gray : danger, availability: (item.up_history / (item.up_history + item.down_history)) * 100, + status: item.summary.down === 0 ? 'up' : 'down', }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx index dbd73fc7d440b..ec5718415595d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx @@ -6,7 +6,8 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiBadge, EuiText } from '@elastic/eui'; +import { EuiBadge, EuiTextColor } from '@elastic/eui'; +import { StatusTag } from './location_status_tags'; const BadgeItem = styled.div` white-space: nowrap; @@ -17,18 +18,13 @@ const BadgeItem = styled.div` } `; -interface Props { - color: string; - label: string; -} - -export const TagLabel: React.FC = ({ color, label }) => { +export const TagLabel: React.FC = ({ color, label, status }) => { return ( - +

{label}

-
+
); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx index bf403846dcec4..8c66b57de58ac 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx @@ -64,9 +64,9 @@ export const MapToolTipComponent = ({ closeTooltip, features = [] }: MapToolTipP <> {layerId === 'up_points' ? ( - + ) : ( - + )} diff --git a/x-pack/test/functional/apps/transform/index.ts b/x-pack/test/functional/apps/transform/index.ts index 04a751279bf3c..6dd22a1f4a204 100644 --- a/x-pack/test/functional/apps/transform/index.ts +++ b/x-pack/test/functional/apps/transform/index.ts @@ -5,9 +5,10 @@ */ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, loadTestFile }: FtrProviderContext) { +export default function ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); + const PageObjects = getPageObjects(['security']); describe('transform', function () { this.tags(['ciGroup9', 'transform']); @@ -30,6 +31,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.unload('ml/ecommerce'); await transform.testResources.resetKibanaTimeZone(); + await PageObjects.security.logout(); }); loadTestFile(require.resolve('./creation_index_pattern')); diff --git a/x-pack/test/functional/es_archives/endpoint/artifacts/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/artifacts/api_feature/data.json index a886b60e7e0dc..b156f2f6cc7bf 100644 --- a/x-pack/test/functional/es_archives/endpoint/artifacts/api_feature/data.json +++ b/x-pack/test/functional/es_archives/endpoint/artifacts/api_feature/data.json @@ -1,20 +1,23 @@ { "type": "doc", "value": { - "id": "endpoint:exceptions-artifact:endpoint-exceptionlist-linux-1.0.0-a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d", + "id": "endpoint:user-artifact:endpoint-exceptionlist-linux-1.0.0-a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d", "index": ".kibana", "source": { "references": [ ], - "endpoint:exceptions-artifact": { + "endpoint:user-artifact": { "body": "eyJleGNlcHRpb25zX2xpc3QiOltdfQ==", "created": 1593016187465, - "encoding": "application/json", + "compressionAlgorithm": "none", + "encryptionAlgorithm": "none", "identifier": "endpoint-exceptionlist-linux-1.0.0", - "sha256": "a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d", - "size": 22 + "compressedSha256": "a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d", + "compressedSize": 22, + "decompressedSha256": "a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d", + "decompressedSize": 22 }, - "type": "endpoint:exceptions-artifact", + "type": "endpoint:user-artifact", "updated_at": "2020-06-24T16:29:47.584Z" } } @@ -23,12 +26,12 @@ { "type": "doc", "value": { - "id": "endpoint:exceptions-manifest:endpoint-manifest-1.0.0", + "id": "endpoint:user-artifact-manifest:endpoint-manifest-1.0.0", "index": ".kibana", "source": { "references": [ ], - "endpoint:exceptions-manifest": { + "endpoint:user-artifact-manifest": { "created": 1593183699663, "ids": [ "endpoint-exceptionlist-linux-1.0.0-a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d", @@ -36,7 +39,7 @@ "endpoint-exceptionlist-windows-1.0.0-a4e4586e895fcb46dd25a25358b446f9a425279452afa3ef9a98bca39c39122d" ] }, - "type": "endpoint:exceptions-manifest", + "type": "endpoint:user-artifact-manifest", "updated_at": "2020-06-26T15:01:39.704Z" } } diff --git a/x-pack/test/plugin_functional/es_archives/global_search/basic/data.json b/x-pack/test/plugin_functional/es_archives/global_search/basic/data.json new file mode 100644 index 0000000000000..f121f6859885b --- /dev/null +++ b/x-pack/test/plugin_functional/es_archives/global_search/basic/data.json @@ -0,0 +1,193 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "index-pattern:logstash-*", + "source": { + "index-pattern": { + "title": "logstash-*", + "timeFieldName": "@timestamp", + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + }, + "type": "index-pattern", + "migrationVersion": { + "index-pattern": "6.5.0" + }, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "visualization:75c3e060-1e7c-11e9-8488-65449e65d0ed", + "source": { + "visualization": { + "title": "A Pie", + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" + } + }, + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "dashboard:i-exist", + "source": { + "dashboard": { + "title": "Amazing Dashboard", + "hits": 0, + "description": "", + "panelsJSON": "[{\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"7.0.0\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"75c3e060-1e7c-11e9-8488-65449e65d0ed\",\"embeddableConfig\":{}}]", + "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}", + "version": 1, + "timeRestore": false, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" + } + }, + "type": "dashboard", + "updated_at": "2019-01-22T19:32:47.232Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "config:6.0.0", + "source": { + "config": { + "buildNum": 9007199254740991, + "defaultIndex": "logstash-*" + }, + "type": "config", + "updated_at": "2019-01-22T19:32:02.235Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "map:0b849ed0-70f5-11e9-8625-9580c4904684", + "source": { + "map": { + "title" : "just a map", + "description" : "", + "mapStateJSON" : "{\"zoom\":0.8,\"center\":{\"lon\":0,\"lat\":0},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}", + "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"id\":\"c7bdee60-5267-459e-83d6-b53acf1b9e67\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"applyGlobalQuery\":true,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\"}]", + "uiStateJSON" : "{\"isLayerTOCOpen\":true}", + "bounds" : { + "type" : "polygon", + "coordinates" : [ + [ + [ + -180, + 85.05113 + ], + [ + -180, + -85.05113 + ], + [ + 180, + -85.05113 + ], + [ + 180, + 85.05113 + ], + [ + -180, + 85.05113 + ] + ] + ] + } + }, + "type": "map", + "references" : [ ], + "migrationVersion" : { + "map" : "7.1.0" + }, + "updated_at" : "2019-05-07T18:22:17.405Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "dashboard:1c1a87f0-70f5-11e9-8625-9580c4904684", + "source": { + "dashboard": { + "title" : "dashboard with map", + "hits" : 0, + "description" : "", + "panelsJSON" : "[{\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"1\"},\"version\":\"8.0.0\",\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":0,\"lon\":0,\"zoom\":0.8},\"isLayerTOCOpen\":true},\"panelRefName\":\"panel_0\"}]", + "optionsJSON" : "{\"useMargins\":true,\"hidePanelTitles\":false}", + "version" : 1, + "timeRestore" : false, + "kibanaSavedObjectMeta" : { + "searchSourceJSON" : "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "type": "dashboard", + "references" : [ + { + "name" : "panel_0", + "type" : "map", + "id" : "0b849ed0-70f5-11e9-8625-9580c4904684" + } + ], + "migrationVersion" : { + "dashboard" : "7.0.0" + }, + "updated_at" : "2019-05-07T18:22:45.231Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "query:okjpgs", + "source": { + "query": { + "title": "OKJpgs", + "description": "Ok responses for jpg files", + "query": { + "query": "response:200", + "language": "kuery" + }, + "filters": [{"meta":{"index":"b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b","alias":null,"negate":false,"type":"phrase","key":"extension.raw","value":"jpg","params":{"query":"jpg"},"disabled":false},"query":{"match":{"extension.raw":{"query":"jpg","type":"phrase"}}},"$state":{"store":"appState"}}] + }, + "type": "query", + "updated_at": "2019-07-17T17:54:26.378Z" + } + } +} diff --git a/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json b/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json new file mode 100644 index 0000000000000..ebb5b19387faf --- /dev/null +++ b/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json @@ -0,0 +1,516 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, + "python": { + "type": "long", + "null_value": 0 + }, + "ruby": { + "type": "long", + "null_value": 0 + } + } + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "id": { + "type": "text", + "index": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "telemetry:optIn": { + "type": "boolean" + } + } + }, + "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" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "map" : { + "properties" : { + "bounds" : { + "type" : "geo_shape", + "tree" : "quadtree" + }, + "description" : { + "type" : "text" + }, + "layerListJSON" : { + "type" : "text" + }, + "mapStateJSON" : { + "type" : "text" + }, + "title" : { + "type" : "text" + }, + "uiStateJSON" : { + "type" : "text" + }, + "version" : { + "type" : "integer" + } + } + }, + "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" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "space": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "namespace": { + "type": "keyword" + }, + "references": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "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" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "enabled": { + "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" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "query": { + "properties": { + "title": { + "type": "text" + }, + "description": { + "type": "text" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "type": "keyword", + "index": false + } + } + }, + "filters": { + "type": "object", + "enabled": false + }, + "timefilter": { + "type": "object", + "enabled": false + } + } + } + } + } + } +} diff --git a/x-pack/test/plugin_functional/plugins/global_search_test/public/plugin.ts b/x-pack/test/plugin_functional/plugins/global_search_test/public/plugin.ts index 27434202d77f1..aba3512788f9c 100644 --- a/x-pack/test/plugin_functional/plugins/global_search_test/public/plugin.ts +++ b/x-pack/test/plugin_functional/plugins/global_search_test/public/plugin.ts @@ -17,7 +17,8 @@ import { createResult } from '../common/utils'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GlobalSearchTestPluginSetup {} export interface GlobalSearchTestPluginStart { - findAll: (term: string) => Promise; + findTest: (term: string) => Promise; + findReal: (term: string) => Promise; } export interface GlobalSearchTestPluginSetupDeps { @@ -74,7 +75,7 @@ export class GlobalSearchTestPlugin { globalSearch }: GlobalSearchTestPluginStartDeps ): GlobalSearchTestPluginStart { return { - findAll: (term) => + findTest: (term) => globalSearch .find(term, {}) .pipe( @@ -84,6 +85,16 @@ export class GlobalSearchTestPlugin reduce((memo, results) => [...memo, ...results]) ) .toPromise(), + findReal: (term) => + globalSearch + .find(term, {}) + .pipe( + map((batch) => batch.results), + // remove test types + map((results) => results.filter((r) => !r.type.startsWith('test_'))), + reduce((memo, results) => [...memo, ...results]) + ) + .toPromise(), }; } } diff --git a/x-pack/test/plugin_functional/test_suites/global_search/global_search_api.ts b/x-pack/test/plugin_functional/test_suites/global_search/global_search_api.ts index ee1745436b735..841c4d2967e21 100644 --- a/x-pack/test/plugin_functional/test_suites/global_search/global_search_api.ts +++ b/x-pack/test/plugin_functional/test_suites/global_search/global_search_api.ts @@ -17,7 +17,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { return browser.executeAsync(async (term, cb) => { const { start } = window.__coreProvider; const globalSearchTestApi: GlobalSearchTestApi = start.plugins.globalSearchTest; - globalSearchTestApi.findAll(term).then(cb); + globalSearchTestApi.findTest(term).then(cb); }, t); }; diff --git a/x-pack/test/plugin_functional/test_suites/global_search/global_search_providers.ts b/x-pack/test/plugin_functional/test_suites/global_search/global_search_providers.ts new file mode 100644 index 0000000000000..4e4f42578d11a --- /dev/null +++ b/x-pack/test/plugin_functional/test_suites/global_search/global_search_providers.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { GlobalSearchResult } from '../../../../plugins/global_search/common/types'; +import { GlobalSearchTestApi } from '../../plugins/global_search_test/public/types'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + + const findResultsWithAPI = async (t: string): Promise => { + return browser.executeAsync(async (term, cb) => { + const { start } = window.__coreProvider; + const globalSearchTestApi: GlobalSearchTestApi = start.plugins.globalSearchTest; + globalSearchTestApi.findReal(term).then(cb); + }, t); + }; + + describe('GlobalSearch - SavedObject provider', function () { + before(async () => { + await esArchiver.load('global_search/basic'); + }); + + after(async () => { + await esArchiver.unload('global_search/basic'); + }); + + beforeEach(async () => { + await pageObjects.common.navigateToApp('globalSearchTestApp'); + }); + + it('can search for index patterns', async () => { + const results = await findResultsWithAPI('logstash'); + expect(results.length).to.be(1); + expect(results[0].type).to.be('index-pattern'); + expect(results[0].title).to.be('logstash-*'); + expect(results[0].score).to.be.greaterThan(1); + }); + + it('can search for visualizations', async () => { + const results = await findResultsWithAPI('pie'); + expect(results.length).to.be(1); + expect(results[0].type).to.be('visualization'); + expect(results[0].title).to.be('A Pie'); + }); + + it('can search for maps', async () => { + const results = await findResultsWithAPI('just'); + expect(results.length).to.be(1); + expect(results[0].type).to.be('map'); + expect(results[0].title).to.be('just a map'); + }); + + it('can search for dashboards', async () => { + const results = await findResultsWithAPI('Amazing'); + expect(results.length).to.be(1); + expect(results[0].type).to.be('dashboard'); + expect(results[0].title).to.be('Amazing Dashboard'); + }); + + it('returns all objects matching the search', async () => { + const results = await findResultsWithAPI('dashboard'); + expect(results.length).to.be.greaterThan(2); + expect(results.map((r) => r.title)).to.contain('dashboard with map'); + expect(results.map((r) => r.title)).to.contain('Amazing Dashboard'); + }); + }); +} diff --git a/x-pack/test/plugin_functional/test_suites/global_search/index.ts b/x-pack/test/plugin_functional/test_suites/global_search/index.ts index 1e5a765612f45..d765e87add105 100644 --- a/x-pack/test/plugin_functional/test_suites/global_search/index.ts +++ b/x-pack/test/plugin_functional/test_suites/global_search/index.ts @@ -10,5 +10,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('GlobalSearch API', function () { this.tags('ciGroup7'); loadTestFile(require.resolve('./global_search_api')); + loadTestFile(require.resolve('./global_search_providers')); }); }