diff --git a/package.json b/package.json index 368985e246891..03e9bd033cf19 100644 --- a/package.json +++ b/package.json @@ -352,7 +352,7 @@ "@babel/traverse": "^7.11.5", "@babel/types": "^7.11.0", "@cypress/snapshot": "^2.1.7", - "@cypress/webpack-preprocessor": "^5.4.11", + "@cypress/webpack-preprocessor": "^5.5.0", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", "@elastic/charts": "24.4.0", @@ -607,7 +607,7 @@ "cpy": "^8.1.1", "cronstrue": "^1.51.0", "css-loader": "^3.4.2", - "cypress": "^6.1.0", + "cypress": "^6.2.1", "cypress-cucumber-preprocessor": "^2.5.2", "cypress-multi-reporters": "^1.4.0", "d3": "3.5.17", diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index d61d544deadc4..5f4e37ee35edf 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -20,7 +20,7 @@ import { Client } from 'elasticsearch'; import { ToolingLog, KbnClient } from '@kbn/dev-utils'; -import { migrateKibanaIndex, deleteKibanaIndices, createStats } from '../lib'; +import { migrateKibanaIndex, createStats, cleanKibanaIndices } from '../lib'; export async function emptyKibanaIndexAction({ client, @@ -32,8 +32,9 @@ export async function emptyKibanaIndexAction({ kbnClient: KbnClient; }) { const stats = createStats('emptyKibanaIndex', log); + const kibanaPluginIds = await kbnClient.plugins.getEnabledIds(); - await deleteKibanaIndices({ client, stats, log }); + await cleanKibanaIndices({ client, stats, log, kibanaPluginIds }); await migrateKibanaIndex({ client, kbnClient }); return stats; } diff --git a/packages/kbn-es-archiver/src/lib/index.ts b/packages/kbn-es-archiver/src/lib/index.ts index 960d51e411859..ac7569ba735ac 100644 --- a/packages/kbn-es-archiver/src/lib/index.ts +++ b/packages/kbn-es-archiver/src/lib/index.ts @@ -25,6 +25,7 @@ export { createGenerateIndexRecordsStream, deleteKibanaIndices, migrateKibanaIndex, + cleanKibanaIndices, createDefaultSpace, } from './indices'; diff --git a/packages/kbn-es-archiver/src/lib/indices/index.ts b/packages/kbn-es-archiver/src/lib/indices/index.ts index 289ac87feb9a5..076582ddde8ab 100644 --- a/packages/kbn-es-archiver/src/lib/indices/index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/index.ts @@ -20,4 +20,9 @@ export { createCreateIndexStream } from './create_index_stream'; export { createDeleteIndexStream } from './delete_index_stream'; export { createGenerateIndexRecordsStream } from './generate_index_records_stream'; -export { migrateKibanaIndex, deleteKibanaIndices, createDefaultSpace } from './kibana_index'; +export { + migrateKibanaIndex, + deleteKibanaIndices, + cleanKibanaIndices, + createDefaultSpace, +} from './kibana_index'; diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index 3599911735b8d..50fabad1fa26f 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -73,6 +73,7 @@ export async function migrateKibanaIndex({ body: { dynamic: true, }, + ignore: [404], } as any); await kbnClient.savedObjects.migrate(); diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts index 5afb8df8502df..75faf3d8c17a7 100644 --- a/src/dev/run_find_plugins_with_circular_deps.ts +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -31,7 +31,6 @@ interface Options { type CircularDepList = Set; const allowedList: CircularDepList = new Set([ - 'src/plugins/vis_default_editor -> src/plugins/visualizations', 'src/plugins/visualizations -> src/plugins/visualize', 'x-pack/plugins/actions -> x-pack/plugins/case', 'x-pack/plugins/case -> x-pack/plugins/security_solution', diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 7b65805a482dd..0d730aed0b28a 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -153,7 +153,14 @@ export function FilterItem(props: Props) { const dataTestSubjNegated = filter.meta.negate ? 'filter-negated' : ''; const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`; const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; - return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned} ${dataTestSubjNegated}`; + return classNames( + 'filter', + dataTestSubjDisabled, + dataTestSubjKey, + dataTestSubjValue, + dataTestSubjPinned, + dataTestSubjNegated + ); } function getPanels() { diff --git a/src/plugins/region_map/kibana.json b/src/plugins/region_map/kibana.json index e679baf6d6f06..4815deba6441d 100644 --- a/src/plugins/region_map/kibana.json +++ b/src/plugins/region_map/kibana.json @@ -15,7 +15,7 @@ ], "requiredBundles": [ "kibanaUtils", - "charts", + "charts", "visDefaultEditor" ] } diff --git a/src/plugins/region_map/public/region_map_type.js b/src/plugins/region_map/public/region_map_type.js index ec32d582ce15b..e7a339a6cc8b6 100644 --- a/src/plugins/region_map/public/region_map_type.js +++ b/src/plugins/region_map/public/region_map_type.js @@ -22,7 +22,6 @@ import { mapToLayerWithId } from './util'; import { createRegionMapVisualization } from './region_map_visualization'; import { RegionMapOptions } from './components/region_map_options'; import { truncatedColorSchemas } from '../../charts/public'; -import { Schemas } from '../../vis_default_editor/public'; import { ORIGIN } from '../../maps_legacy/public'; import { getDeprecationMessage } from './get_deprecation_message'; @@ -64,7 +63,7 @@ provided base maps, or add your own. Darker colors represent higher values.', vectorLayers: [], tmsLayers: [], }, - schemas: new Schemas([ + schemas: [ { group: 'metrics', name: 'metric', @@ -98,7 +97,7 @@ provided base maps, or add your own. Darker colors represent higher values.', max: 1, aggFilter: ['terms'], }, - ]), + ], }, setup: async (vis) => { const serviceSettings = await getServiceSettings(); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 2e262ce43731a..518b1831abda8 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -1,200 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SavedObjectsTable delete should show a confirm modal 1`] = ` - - } - confirmButtonText={ - - } - defaultFocusedButton="confirm" + + selectedObjects={ + Array [ + Object { + "id": "1", + "type": "index-pattern", + }, + Object { + "id": "3", + "type": "dashboard", + }, + ] } -> -

- -

- -
+/> `; exports[`SavedObjectsTable export should allow the user to choose when exporting all 1`] = ` - - - - - - - - - } - labelType="legend" - > - - - - - } - name="includeReferencesDeep" - onChange={[Function]} - /> - - - - - - - - - - - - - - - - - - - - + `; exports[`SavedObjectsTable should render normally 1`] = ` diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx new file mode 100644 index 0000000000000..db1f83759fad5 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx @@ -0,0 +1,97 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { SavedObjectWithMetadata } from '../../../../common'; +import { DeleteConfirmModal } from './delete_confirm_modal'; + +const createObject = (): SavedObjectWithMetadata => ({ + id: 'foo', + type: 'bar', + attributes: {}, + references: [], + meta: {}, +}); + +describe('DeleteConfirmModal', () => { + let onConfirm: jest.Mock; + let onCancel: jest.Mock; + + beforeEach(() => { + onConfirm = jest.fn(); + onCancel = jest.fn(); + }); + + it('displays a loader if `isDeleting` is true', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('EuiLoadingElastic')).toHaveLength(1); + expect(wrapper.find('EuiModal')).toHaveLength(0); + }); + + it('lists the objects to delete', () => { + const objs = [createObject(), createObject(), createObject()]; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('.euiTableRow')).toHaveLength(3); + }); + + it('calls `onCancel` when clicking on the cancel button', () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('EuiButtonEmpty').simulate('click'); + + expect(onCancel).toHaveBeenCalledTimes(1); + expect(onConfirm).not.toHaveBeenCalled(); + }); + + it('calls `onDelete` when clicking on the delete button', () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('EuiButton').simulate('click'); + + expect(onConfirm).toHaveBeenCalledTimes(1); + expect(onCancel).not.toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx new file mode 100644 index 0000000000000..07564843e9745 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx @@ -0,0 +1,153 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC } from 'react'; +import { + EuiInMemoryTable, + EuiLoadingElastic, + EuiToolTip, + EuiIcon, + EuiOverlayMask, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectWithMetadata } from '../../../../common'; +import { getSavedObjectLabel } from '../../../lib'; + +export interface DeleteConfirmModalProps { + isDeleting: boolean; + onConfirm: () => void; + onCancel: () => void; + selectedObjects: SavedObjectWithMetadata[]; +} + +export const DeleteConfirmModal: FC = ({ + isDeleting, + onConfirm, + onCancel, + selectedObjects, +}) => { + if (isDeleting) { + return ( + + + + ); + } + + // can't use `EuiConfirmModal` here as the confirm modal body is wrapped + // inside a `

` element, causing UI glitches with the table. + return ( + + + + + + + + +

+ +

+ + ( + + + + ), + }, + { + field: 'id', + name: i18n.translate( + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName', + { defaultMessage: 'Id' } + ), + }, + { + field: 'meta.title', + name: i18n.translate( + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', + { defaultMessage: 'Title' } + ), + }, + ]} + pagination={true} + sorting={false} + /> + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx new file mode 100644 index 0000000000000..c76c5b68cd66f --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { ExportModal } from './export_modal'; + +describe('ExportModal', () => { + let onExport: jest.Mock; + let onCancel: jest.Mock; + let onSelectedOptionsChange: jest.Mock; + let onIncludeReferenceChange: jest.Mock; + + const options = [ + { id: '1', label: 'option 1' }, + { id: '2', label: 'option 2' }, + ]; + const selectedOptions = { + 1: true, + 2: false, + }; + + beforeEach(() => { + onExport = jest.fn(); + onCancel = jest.fn(); + onSelectedOptionsChange = jest.fn(); + onIncludeReferenceChange = jest.fn(); + }); + + it('Displays a checkbox for each option', () => { + const wrapper = mountWithIntl( + + ); + + expect(wrapper.find('EuiCheckbox')).toHaveLength(2); + }); + + it('calls `onCancel` when clicking on the cancel button', () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('EuiButtonEmpty').simulate('click'); + + expect(onCancel).toHaveBeenCalledTimes(1); + expect(onExport).not.toHaveBeenCalled(); + }); + + it('calls `onExport` when clicking on the export button', () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('EuiButton').simulate('click'); + + expect(onExport).toHaveBeenCalledTimes(1); + expect(onCancel).not.toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx new file mode 100644 index 0000000000000..01ef145bcd077 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx @@ -0,0 +1,137 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC } from 'react'; +import { + EuiOverlayMask, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + EuiSpacer, + EuiFormRow, + EuiCheckboxGroup, + EuiSwitch, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export interface ExportModalProps { + onExport: () => void; + onCancel: () => void; + onSelectedOptionsChange: (newSelectedOptions: Record) => void; + filteredItemCount: number; + options: Array<{ id: string; label: string }>; + selectedOptions: Record; + includeReferences: boolean; + onIncludeReferenceChange: (newIncludeReference: boolean) => void; +} + +export const ExportModal: FC = ({ + onCancel, + onExport, + onSelectedOptionsChange, + options, + filteredItemCount, + selectedOptions, + includeReferences, + onIncludeReferenceChange, +}) => { + return ( + + + + + + + + + + } + labelType="legend" + > + { + onSelectedOptionsChange({ + ...selectedOptions, + ...{ + [optionId]: !selectedOptions[optionId], + }, + }); + }} + /> + + + + } + checked={includeReferences} + onChange={() => onIncludeReferenceChange(!includeReferences)} + /> + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts b/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts index 9c8736a9011eb..23e681b92b269 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts @@ -21,3 +21,5 @@ export { Header } from './header'; export { Table } from './table'; export { Flyout } from './flyout'; export { Relationships } from './relationships'; +export { DeleteConfirmModal } from './delete_confirm_modal'; +export { ExportModal } from './export_modal'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 0171c13fd974b..1991a60018aa0 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -325,7 +325,7 @@ describe('SavedObjectsTable', () => { (component.find('Header') as any).prop('onExportAll')(); component.update(); - expect(component.find('EuiModal')).toMatchSnapshot(); + expect(component.find('ExportModal')).toMatchSnapshot(); }); it('should export all', async () => { @@ -504,7 +504,7 @@ describe('SavedObjectsTable', () => { await component.instance().onDelete(); component.update(); - expect(component.find('EuiConfirmModal')).toMatchSnapshot(); + expect(component.find('DeleteConfirmModal')).toMatchSnapshot(); }); it('should delete selected objects', async () => { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index a5a4bcab364af..bb158b7621125 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -21,32 +21,8 @@ import React, { Component } from 'react'; import { debounce } from 'lodash'; // @ts-expect-error import { saveAs } from '@elastic/filesaver'; -import { - EuiSpacer, - Query, - EuiInMemoryTable, - EuiIcon, - EuiConfirmModal, - EuiLoadingElastic, - EuiOverlayMask, - EUI_MODAL_CONFIRM_BUTTON, - EuiCheckboxGroup, - EuiToolTip, - EuiPageContent, - EuiSwitch, - EuiModal, - EuiModalHeader, - EuiModalBody, - EuiModalFooter, - EuiButtonEmpty, - EuiButton, - EuiModalHeaderTitle, - EuiFormRow, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; +import { EuiSpacer, Query, EuiPageContent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { SavedObjectsClientContract, SavedObjectsFindOptions, @@ -62,7 +38,6 @@ import { parseQuery, getSavedObjectCounts, getRelationships, - getSavedObjectLabel, fetchExportObjects, fetchExportByTypeAndSearch, findObjects, @@ -77,7 +52,14 @@ import { SavedObjectsManagementActionServiceStart, SavedObjectsManagementColumnServiceStart, } from '../../services'; -import { Header, Table, Flyout, Relationships } from './components'; +import { + Header, + Table, + Flyout, + Relationships, + DeleteConfirmModal, + ExportModal, +} from './components'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; interface ExportAllOption { @@ -554,114 +536,24 @@ export class SavedObjectsTable extends Component; - } else { - const onCancel = () => { - this.setState({ isShowingDeleteConfirmModal: false }); - }; - - const onConfirm = () => { - this.delete(); - }; - - modal = ( - - } - onCancel={onCancel} - onConfirm={onConfirm} - buttonColor="danger" - cancelButtonText={ - - } - confirmButtonText={ - isDeleting ? ( - - ) : ( - - ) - } - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - > -

- -

- ( - - - - ), - }, - { - field: 'id', - name: i18n.translate( - 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName', - { defaultMessage: 'Id' } - ), - }, - { - field: 'meta.title', - name: i18n.translate( - 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', - { defaultMessage: 'Title' } - ), - }, - ]} - pagination={true} - sorting={false} - /> -
- ); - } - - return {modal}; + return ( + { + this.delete(); + }} + onCancel={() => { + this.setState({ isShowingDeleteConfirmModal: false }); + }} + selectedObjects={selectedSavedObjects} + /> + ); } - changeIncludeReferencesDeep = () => { - this.setState((state) => ({ - isIncludeReferencesDeepChecked: !state.isIncludeReferencesDeepChecked, - })); - }; - - closeExportAllModal = () => { - this.setState({ isShowingExportAllOptionsModal: false }); - }; - renderExportAllOptionsModal() { const { isShowingExportAllOptionsModal, @@ -676,85 +568,26 @@ export class SavedObjectsTable extends Component - - - - - - - - - } - labelType="legend" - > - { - const newExportAllSelectedOptions = { - ...exportAllSelectedOptions, - ...{ - [optionId]: !exportAllSelectedOptions[optionId], - }, - }; - - this.setState({ - exportAllSelectedOptions: newExportAllSelectedOptions, - }); - }} - /> - - - - } - checked={isIncludeReferencesDeepChecked} - onChange={this.changeIncludeReferencesDeep} - /> - - - - - - - - - - - - - - - - - - - - - + { + this.setState({ isShowingExportAllOptionsModal: false }); + }} + onSelectedOptionsChange={(newOptions) => { + this.setState({ + exportAllSelectedOptions: newOptions, + }); + }} + filteredItemCount={filteredItemCount} + options={exportAllOptions} + selectedOptions={exportAllSelectedOptions} + includeReferences={isIncludeReferencesDeepChecked} + onIncludeReferenceChange={(newIncludeReferences) => { + this.setState({ + isIncludeReferencesDeepChecked: newIncludeReferences, + }); + }} + /> ); } diff --git a/src/plugins/tile_map/public/tile_map_type.js b/src/plugins/tile_map/public/tile_map_type.js index c5e3f0d578e30..3e9b5516322d9 100644 --- a/src/plugins/tile_map/public/tile_map_type.js +++ b/src/plugins/tile_map/public/tile_map_type.js @@ -20,7 +20,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { convertToGeoJson, MapTypes } from '../../maps_legacy/public'; -import { Schemas } from '../../vis_default_editor/public'; import { createTileMapVisualization } from './tile_map_visualization'; import { TileMapOptions } from './components/tile_map_options'; import { supportsCssFilters } from './css_filters'; @@ -115,7 +114,7 @@ export function createTileMapTypeDefinition(dependencies) { tmsLayers: [], }, optionsTemplate: (props) => , - schemas: new Schemas([ + schemas: [ { group: 'metrics', name: 'metric', @@ -137,7 +136,7 @@ export function createTileMapTypeDefinition(dependencies) { min: 1, max: 1, }, - ]), + ], }, setup: async (vis) => { let tmsLayers; diff --git a/src/plugins/vis_default_editor/public/components/agg.test.tsx b/src/plugins/vis_default_editor/public/components/agg.test.tsx index db107fa589083..704037fd35a41 100644 --- a/src/plugins/vis_default_editor/public/components/agg.test.tsx +++ b/src/plugins/vis_default_editor/public/components/agg.test.tsx @@ -22,11 +22,11 @@ import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { IndexPattern, IAggType, AggGroupNames } from 'src/plugins/data/public'; +import type { Schema } from '../../../visualizations/public'; import { DefaultEditorAgg, DefaultEditorAggProps } from './agg'; import { DefaultEditorAggParams } from './agg_params'; import { AGGS_ACTION_KEYS } from './agg_group_state'; -import { Schema } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; jest.mock('./agg_params', () => ({ diff --git a/src/plugins/vis_default_editor/public/components/agg_add.tsx b/src/plugins/vis_default_editor/public/components/agg_add.tsx index 2da7b33139a8e..50913d2058489 100644 --- a/src/plugins/vis_default_editor/public/components/agg_add.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_add.tsx @@ -30,7 +30,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { IAggConfig, AggGroupNames } from '../../../data/public'; -import { Schema } from '../schemas'; +import type { Schema } from '../../../visualizations/public'; interface DefaultEditorAggAddProps { group?: IAggConfig[]; diff --git a/src/plugins/vis_default_editor/public/components/agg_common_props.ts b/src/plugins/vis_default_editor/public/components/agg_common_props.ts index 40d7b79bfbefc..998a767d841ec 100644 --- a/src/plugins/vis_default_editor/public/components/agg_common_props.ts +++ b/src/plugins/vis_default_editor/public/components/agg_common_props.ts @@ -17,10 +17,10 @@ * under the License. */ -import { VisParams } from 'src/plugins/visualizations/public'; -import { IAggType, IAggConfig, AggGroupName } from 'src/plugins/data/public'; -import { Schema } from '../schemas'; -import { EditorVisState } from './sidebar/state/reducers'; +import type { VisParams, Schema } from 'src/plugins/visualizations/public'; +import type { IAggType, IAggConfig, AggGroupName } from 'src/plugins/data/public'; + +import type { EditorVisState } from './sidebar/state/reducers'; type AggId = IAggConfig['id']; type AggParams = IAggConfig['params']; diff --git a/src/plugins/vis_default_editor/public/components/agg_group.test.tsx b/src/plugins/vis_default_editor/public/components/agg_group.test.tsx index 483446daed10f..86b17b0e160bd 100644 --- a/src/plugins/vis_default_editor/public/components/agg_group.test.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_group.test.tsx @@ -20,21 +20,28 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import { IAggConfigs, IAggConfig } from 'src/plugins/data/public'; +import type { IAggConfigs, IAggConfig } from 'src/plugins/data/public'; +import { ISchemas } from 'src/plugins/visualizations/public'; +import { createMockedVisEditorSchemas } from 'src/plugins/visualizations/public/mocks'; + import { DefaultEditorAggGroup, DefaultEditorAggGroupProps } from './agg_group'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; -import { ISchemas, Schemas } from '../schemas'; -import { EditorVisState } from './sidebar/state/reducers'; - -jest.mock('@elastic/eui', () => ({ - EuiTitle: 'eui-title', - EuiDragDropContext: 'eui-drag-drop-context', - EuiDroppable: 'eui-droppable', - EuiDraggable: (props: any) => props.children({ dragHandleProps: {} }), - EuiSpacer: 'eui-spacer', - EuiPanel: 'eui-panel', -})); +import type { EditorVisState } from './sidebar/state/reducers'; + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + EuiTitle: 'eui-title', + EuiDragDropContext: 'eui-drag-drop-context', + EuiDroppable: 'eui-droppable', + EuiDraggable: (props: any) => props.children({ dragHandleProps: {} }), + EuiSpacer: 'eui-spacer', + EuiPanel: 'eui-panel', + }; +}); jest.mock('./agg', () => ({ DefaultEditorAgg: () =>
, @@ -56,7 +63,7 @@ describe('DefaultEditorAgg component', () => { setTouched = jest.fn(); setValidity = jest.fn(); reorderAggs = jest.fn(); - schemas = new Schemas([ + schemas = createMockedVisEditorSchemas([ { name: 'metrics', group: 'metrics', diff --git a/src/plugins/vis_default_editor/public/components/agg_group.tsx b/src/plugins/vis_default_editor/public/components/agg_group.tsx index 4cde33b8fbc31..76bc9ca881b98 100644 --- a/src/plugins/vis_default_editor/public/components/agg_group.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_group.tsx @@ -31,6 +31,7 @@ import { import { i18n } from '@kbn/i18n'; import { AggGroupNames, AggGroupLabels, IAggConfig, TimeRange } from '../../../data/public'; +import type { Schema } from '../../../visualizations/public'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props'; @@ -41,7 +42,6 @@ import { getEnabledMetricAggsCount, } from './agg_group_helper'; import { aggGroupReducer, initAggsState, AGGS_ACTION_KEYS } from './agg_group_state'; -import { Schema } from '../schemas'; export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { schemas: Schema[]; diff --git a/src/plugins/vis_default_editor/public/components/agg_group_helper.test.ts b/src/plugins/vis_default_editor/public/components/agg_group_helper.test.ts index 3693f1b1e3091..821796d417278 100644 --- a/src/plugins/vis_default_editor/public/components/agg_group_helper.test.ts +++ b/src/plugins/vis_default_editor/public/components/agg_group_helper.test.ts @@ -17,7 +17,9 @@ * under the License. */ -import { IAggConfig } from 'src/plugins/data/public'; +import type { IAggConfig } from 'src/plugins/data/public'; +import type { Schema } from 'src/plugins/visualizations/public'; + import { isAggRemovable, calcAggIsTooLow, @@ -25,7 +27,6 @@ import { getEnabledMetricAggsCount, } from './agg_group_helper'; import { AggsState } from './agg_group_state'; -import { Schema } from '../schemas'; describe('DefaultEditorGroup helpers', () => { let group: IAggConfig[]; diff --git a/src/plugins/vis_default_editor/public/components/agg_group_helper.tsx b/src/plugins/vis_default_editor/public/components/agg_group_helper.tsx index a5a949ce66c82..9fe24e3b52b24 100644 --- a/src/plugins/vis_default_editor/public/components/agg_group_helper.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_group_helper.tsx @@ -18,9 +18,11 @@ */ import { findIndex, isEmpty } from 'lodash'; -import { IAggConfig } from 'src/plugins/data/public'; -import { AggsState } from './agg_group_state'; -import { Schema, getSchemaByName } from '../schemas'; +import type { IAggConfig } from 'src/plugins/data/public'; +import type { Schema } from 'src/plugins/visualizations/public'; + +import { getSchemaByName } from '../schemas'; +import type { AggsState } from './agg_group_state'; const isAggRemovable = (agg: IAggConfig, group: IAggConfig[], schemas: Schema[]) => { const schema = getSchemaByName(schemas, agg.schema); diff --git a/src/plugins/vis_default_editor/public/components/agg_param_props.ts b/src/plugins/vis_default_editor/public/components/agg_param_props.ts index 076bddc9551ea..dd6bac6bb25f5 100644 --- a/src/plugins/vis_default_editor/public/components/agg_param_props.ts +++ b/src/plugins/vis_default_editor/public/components/agg_param_props.ts @@ -23,9 +23,9 @@ import { IndexPatternField, OptionedValueProp, } from 'src/plugins/data/public'; +import type { Schema } from 'src/plugins/visualizations/public'; import { ComboBoxGroupedOptions } from '../utils'; import { EditorConfig } from './utils'; -import { Schema } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; // NOTE: we cannot export the interface with export { InterfaceName } diff --git a/src/plugins/vis_default_editor/public/components/agg_params.tsx b/src/plugins/vis_default_editor/public/components/agg_params.tsx index 78398d8359e9e..662cc641ef7bf 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_params.tsx @@ -23,6 +23,7 @@ import { i18n } from '@kbn/i18n'; import useUnmount from 'react-use/lib/useUnmount'; import { IAggConfig, IndexPattern, AggGroupNames } from '../../../data/public'; +import type { Schema } from '../../../visualizations/public'; import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; @@ -39,7 +40,7 @@ import { } from './agg_params_state'; import { DefaultEditorCommonProps } from './agg_common_props'; import { EditorParamConfig, TimeIntervalParam, FixedParam, getEditorConfig } from './utils'; -import { Schema, getSchemaByName } from '../schemas'; +import { getSchemaByName } from '../schemas'; import { useKibana } from '../../../kibana_react/public'; import { VisDefaultEditorKibanaServices } from '../types'; diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts index a56155db02f6b..df79a48e4f11f 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts @@ -24,6 +24,8 @@ import { IAggType, IndexPattern, } from 'src/plugins/data/public'; +import type { Schema } from 'src/plugins/visualizations/public'; + import { getAggParamsToRender, getAggTypeOptions, @@ -31,7 +33,6 @@ import { } from './agg_params_helper'; import { FieldParamEditor, OrderByParamEditor } from './controls'; import { EditorConfig } from './utils'; -import { Schema } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; import { groupAndSortBy } from '../utils'; diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts index 271fc75a0853e..5b2ccb20e2ac9 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts @@ -27,13 +27,15 @@ import { IndexPattern, IndexPatternField, } from '../../../data/public'; +import type { Schema } from '../../../visualizations/public'; + import { filterAggTypes, filterAggTypeFields } from '../agg_filters'; import { groupAndSortBy, ComboBoxGroupedOptions } from '../utils'; import { AggTypeState, AggParamsState } from './agg_params_state'; import { AggParamEditorProps } from './agg_param_props'; import { aggParamsMap } from './agg_params_map'; import { EditorConfig } from './utils'; -import { Schema, getSchemaByName } from '../schemas'; +import { getSchemaByName } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; interface ParamInstanceBase { diff --git a/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx b/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx index b2c7bcafa15a3..8c213c6f77261 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx @@ -28,6 +28,7 @@ import { search, TimeRange, } from '../../../../data/public'; +import type { ISchemas } from '../../../../visualizations/public'; import { DefaultEditorAggGroup } from '../agg_group'; import { EditorAction, @@ -38,9 +39,8 @@ import { changeAggType, toggleEnabledAgg, } from './state'; -import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props'; -import { ISchemas } from '../../schemas'; -import { EditorVisState } from './state/reducers'; +import type { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props'; +import type { EditorVisState } from './state/reducers'; export interface DefaultEditorDataTabProps { dispatch: React.Dispatch; diff --git a/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx index d6b69a769e0a3..d4116375fd796 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx @@ -28,6 +28,7 @@ import { PersistedState, VisualizeEmbeddableContract, } from 'src/plugins/visualizations/public'; +import type { Schema } from 'src/plugins/visualizations/public'; import { TimeRange } from 'src/plugins/data/public'; import { SavedObject } from 'src/plugins/saved_objects/public'; import { DefaultEditorNavBar } from './navbar'; @@ -35,7 +36,6 @@ import { DefaultEditorControls } from './controls'; import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state'; import { DefaultEditorAggCommonProps } from '../agg_common_props'; import { SidebarTitle } from './sidebar_title'; -import { Schema } from '../../schemas'; import { useOptionTabs } from './use_option_tabs'; interface DefaultEditorSideBarProps { diff --git a/src/plugins/vis_default_editor/public/components/sidebar/state/actions.ts b/src/plugins/vis_default_editor/public/components/sidebar/state/actions.ts index 0e2724ecc08a8..bc02a15c61aeb 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/state/actions.ts +++ b/src/plugins/vis_default_editor/public/components/sidebar/state/actions.ts @@ -17,10 +17,10 @@ * under the License. */ -import { Vis, VisParams } from 'src/plugins/visualizations/public'; -import { IAggConfig } from 'src/plugins/data/public'; +import { Vis, VisParams, Schema } from 'src/plugins/visualizations/public'; +import type { IAggConfig } from 'src/plugins/data/public'; + import { EditorStateActionTypes } from './constants'; -import { Schema } from '../../../schemas'; export interface ActionType { type: T; diff --git a/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts b/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts index 90e2d792d3597..93186b8ffcb9b 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts +++ b/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts @@ -35,7 +35,7 @@ export interface OptionTab { export const useOptionTabs = ({ type: visType }: Vis): [OptionTab[], (name: string) => void] => { const [optionTabs, setOptionTabs] = useState(() => { const tabs = [ - ...(visType.schemas.buckets || visType.schemas.metrics + ...(visType.schemas.buckets?.length || visType.schemas.metrics?.length ? [ { name: 'data', diff --git a/src/plugins/vis_default_editor/public/index.ts b/src/plugins/vis_default_editor/public/index.ts index fd1bdf8b2e65d..06834ab19c876 100644 --- a/src/plugins/vis_default_editor/public/index.ts +++ b/src/plugins/vis_default_editor/public/index.ts @@ -28,7 +28,6 @@ export { RangesParamEditor, RangeValues } from './components/controls/ranges'; export * from './editor_size'; export * from './vis_options_props'; export * from './utils'; -export { ISchemas, Schemas, Schema } from './schemas'; export const plugin = (context: PluginInitializerContext) => { return new VisDefaultEditorPlugin(); diff --git a/src/plugins/vis_default_editor/public/schemas.ts b/src/plugins/vis_default_editor/public/schemas.ts index 7ecb4e54726b8..204f2eb68bec6 100644 --- a/src/plugins/vis_default_editor/public/schemas.ts +++ b/src/plugins/vis_default_editor/public/schemas.ts @@ -17,86 +17,7 @@ * under the License. */ -import { ReactNode } from 'react'; -import _, { defaults } from 'lodash'; - -import { Optional } from '@kbn/utility-types'; - -import { AggGroupNames, AggParam, AggGroupName } from '../../data/public'; - -export interface ISchemas { - [AggGroupNames.Buckets]: Schema[]; - [AggGroupNames.Metrics]: Schema[]; - all: Schema[]; -} - -export interface Schema { - aggFilter: string[]; - editor: boolean | string; - group: AggGroupName; - max: number; - min: number; - name: string; - params: AggParam[]; - title: string; - defaults: unknown; - hideCustomLabel?: boolean; - mustBeFirst?: boolean; - aggSettings?: any; - disabled?: boolean; - tooltip?: ReactNode; -} - -export class Schemas implements ISchemas { - all: Schema[] = []; - [AggGroupNames.Buckets]: Schema[] = []; - [AggGroupNames.Metrics]: Schema[] = []; - - constructor( - schemas: Array< - Optional< - Schema, - 'min' | 'max' | 'group' | 'title' | 'aggFilter' | 'editor' | 'params' | 'defaults' - > - > - ) { - _(schemas || []) - .chain() - .map((schema) => { - if (!schema.name) throw new Error('all schema must have a unique name'); - - if (schema.name === 'split') { - schema.params = [ - { - name: 'row', - default: true, - }, - ] as AggParam[]; - } - - defaults(schema, { - min: 0, - max: Infinity, - group: AggGroupNames.Buckets, - title: schema.name, - aggFilter: '*', - editor: false, - params: [], - }); - - return schema as Schema; - }) - .tap((fullSchemas: Schema[]) => { - this.all = fullSchemas; - }) - .groupBy('group') - .forOwn((group, groupName) => { - // @ts-ignore - this[groupName] = group; - }) - .commit(); - } -} +import type { Schema } from '../../visualizations/public'; export const getSchemaByName = (schemas: Schema[], schemaName?: string) => { return schemas.find((s) => s.name === schemaName) || ({} as Schema); diff --git a/src/plugins/vis_type_metric/public/metric_vis_type.ts b/src/plugins/vis_type_metric/public/metric_vis_type.ts index ba8f27b9412a2..75607de810c63 100644 --- a/src/plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_type.ts @@ -18,11 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; import { MetricVisOptions } from './components/metric_vis_options'; import { ColorSchemas, colorSchemas, ColorMode } from '../../charts/public'; +import { BaseVisTypeOptions } from '../../visualizations/public'; import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; import { toExpressionAst } from './to_ast'; export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({ @@ -83,7 +82,7 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({ colorSchemas, }, optionsTemplate: MetricVisOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -120,6 +119,6 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({ max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], }, - ]), + ], }, }); diff --git a/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts b/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts index 5aef3fc26fa6c..367c120dc8888 100644 --- a/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts +++ b/src/plugins/vis_type_table/public/legacy/table_vis_legacy_type.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; import { AggGroupNames } from '../../../data/public'; -import { Schemas } from '../../../vis_default_editor/public'; import { BaseVisTypeOptions } from '../../../visualizations/public'; import { TableOptions } from '../components/table_vis_options_lazy'; @@ -54,7 +53,7 @@ export const tableVisLegacyTypeDefinition: BaseVisTypeOptions = }, editorConfig: { optionsTemplate: TableOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -88,7 +87,7 @@ export const tableVisLegacyTypeDefinition: BaseVisTypeOptions = max: 1, aggFilter: ['!filter'], }, - ]), + ], }, toExpressionAst, hierarchicalData: (vis) => vis.params.showPartialRows || vis.params.showMetricsAtAllLevels, diff --git a/src/plugins/vis_type_table/public/table_vis_type.ts b/src/plugins/vis_type_table/public/table_vis_type.ts index bfe1427d38496..b15a1a5c03ce3 100644 --- a/src/plugins/vis_type_table/public/table_vis_type.ts +++ b/src/plugins/vis_type_table/public/table_vis_type.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; import { BaseVisTypeOptions } from '../../visualizations/public'; import { TableOptions } from './components/table_vis_options_lazy'; @@ -51,7 +50,7 @@ export const tableVisTypeDefinition: BaseVisTypeOptions = { }, editorConfig: { optionsTemplate: TableOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -85,7 +84,7 @@ export const tableVisTypeDefinition: BaseVisTypeOptions = { max: 1, aggFilter: ['!filter'], }, - ]), + ], }, toExpressionAst, hierarchicalData: (vis) => vis.params.showPartialRows || vis.params.showMetricsAtAllLevels, diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts index 71d4408ddc767..8332f7a6615a6 100644 --- a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts +++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts @@ -18,8 +18,6 @@ */ import { i18n } from '@kbn/i18n'; - -import { Schemas } from '../../vis_default_editor/public'; import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; import { TagCloudOptions } from './components/tag_cloud_options'; @@ -89,7 +87,7 @@ export const tagCloudVisTypeDefinition = { ], }, optionsTemplate: TagCloudOptions, - schemas: new Schemas([ + schemas: [ { group: 'metrics', name: 'metric', @@ -118,6 +116,6 @@ export const tagCloudVisTypeDefinition = { max: 1, aggFilter: ['terms', 'significant_terms'], }, - ]), + ], }, }; diff --git a/src/plugins/vis_type_vislib/public/gauge.ts b/src/plugins/vis_type_vislib/public/gauge.ts index de32ee17a21bf..ef34399b1d851 100644 --- a/src/plugins/vis_type_vislib/public/gauge.ts +++ b/src/plugins/vis_type_vislib/public/gauge.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ColorMode, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public'; -import { RangeValues, Schemas } from '../../vis_default_editor/public'; +import { RangeValues } from '../../vis_default_editor/public'; import { AggGroupNames } from '../../data/public'; import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; @@ -115,7 +115,7 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions = { editorConfig: { collections: getGaugeCollections(), optionsTemplate: GaugeOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -144,7 +144,7 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions = { max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], }, - ]), + ], }, useCustomNoDataScreen: true, }; diff --git a/src/plugins/vis_type_vislib/public/goal.ts b/src/plugins/vis_type_vislib/public/goal.ts index 56361421261fc..3b8153048a861 100644 --- a/src/plugins/vis_type_vislib/public/goal.ts +++ b/src/plugins/vis_type_vislib/public/goal.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; import { ColorMode, ColorSchemas } from '../../charts/public'; import { BaseVisTypeOptions } from '../../visualizations/public'; @@ -79,7 +78,7 @@ export const goalVisTypeDefinition: BaseVisTypeOptions = { editorConfig: { collections: getGaugeCollections(), optionsTemplate: GaugeOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -108,7 +107,7 @@ export const goalVisTypeDefinition: BaseVisTypeOptions = { max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], }, - ]), + ], }, useCustomNoDataScreen: true, }; diff --git a/src/plugins/vis_type_vislib/public/heatmap.ts b/src/plugins/vis_type_vislib/public/heatmap.ts index 4a815fd8b2c73..c8eeff406b7fa 100644 --- a/src/plugins/vis_type_vislib/public/heatmap.ts +++ b/src/plugins/vis_type_vislib/public/heatmap.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { Position } from '@elastic/charts'; -import { RangeValues, Schemas } from '../../vis_default_editor/public'; +import { RangeValues } from '../../vis_default_editor/public'; import { AggGroupNames } from '../../data/public'; import { ColorSchemas, ColorSchemaParams } from '../../charts/public'; import { VIS_EVENT_TO_TRIGGER, BaseVisTypeOptions } from '../../visualizations/public'; @@ -88,7 +88,7 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions = { editorConfig: { collections: getHeatmapCollections(), optionsTemplate: HeatmapOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -136,6 +136,6 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions = { max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], }, - ]), + ], }, }; diff --git a/src/plugins/vis_type_vislib/public/pie.ts b/src/plugins/vis_type_vislib/public/pie.ts index aa5a3ceaaba98..a58784405541f 100644 --- a/src/plugins/vis_type_vislib/public/pie.ts +++ b/src/plugins/vis_type_vislib/public/pie.ts @@ -21,7 +21,6 @@ import { i18n } from '@kbn/i18n'; import { Position } from '@elastic/charts'; import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; import { getPositions } from '../../vis_type_xy/public'; @@ -69,7 +68,7 @@ export const pieVisTypeDefinition: BaseVisTypeOptions = { legendPositions: getPositions(), }, optionsTemplate: PieOptions, - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -102,7 +101,7 @@ export const pieVisTypeDefinition: BaseVisTypeOptions = { max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], }, - ]), + ], }, hierarchicalData: true, responseHandler: 'vislib_slices', diff --git a/src/plugins/vis_type_xy/public/vis_types/area.tsx b/src/plugins/vis_type_xy/public/vis_types/area.tsx index 9529456f17d55..58423d2f619fa 100644 --- a/src/plugins/vis_type_xy/public/vis_types/area.tsx +++ b/src/plugins/vis_type_xy/public/vis_types/area.tsx @@ -24,7 +24,6 @@ import { i18n } from '@kbn/i18n'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Fit, Position } from '@elastic/charts'; -import { Schemas } from '../../../vis_default_editor/public'; import { AggGroupNames } from '../../../data/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../visualizations/public'; import { defaultCountLabel, LabelRotation } from '../../../charts/public'; @@ -139,7 +138,7 @@ export const getAreaVisTypeDefinition = ( editorConfig: { collections: getConfigCollections(), optionTabs: getOptionTabs(showElasticChartsOptions), - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -196,6 +195,6 @@ export const getAreaVisTypeDefinition = ( tooltip: , }), }, - ]), + ], }, }); diff --git a/src/plugins/vis_type_xy/public/vis_types/histogram.tsx b/src/plugins/vis_type_xy/public/vis_types/histogram.tsx index 87fcd53729f57..5bc5f1b49e5da 100644 --- a/src/plugins/vis_type_xy/public/vis_types/histogram.tsx +++ b/src/plugins/vis_type_xy/public/vis_types/histogram.tsx @@ -24,7 +24,6 @@ import { i18n } from '@kbn/i18n'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position } from '@elastic/charts'; -import { Schemas } from '../../../vis_default_editor/public'; import { AggGroupNames } from '../../../data/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../visualizations/public'; @@ -142,7 +141,7 @@ export const getHistogramVisTypeDefinition = ( editorConfig: { collections: getConfigCollections(), optionTabs: getOptionTabs(showElasticChartsOptions), - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -199,6 +198,6 @@ export const getHistogramVisTypeDefinition = ( tooltip: , }), }, - ]), + ], }, }); diff --git a/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.tsx b/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.tsx index 2806cb8e14983..3029b3dcd6765 100644 --- a/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.tsx +++ b/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.tsx @@ -24,7 +24,6 @@ import { i18n } from '@kbn/i18n'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position } from '@elastic/charts'; -import { Schemas } from '../../../vis_default_editor/public'; import { AggGroupNames } from '../../../data/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../visualizations/public'; @@ -141,7 +140,7 @@ export const getHorizontalBarVisTypeDefinition = ( editorConfig: { collections: getConfigCollections(), optionTabs: getOptionTabs(showElasticChartsOptions), - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -198,6 +197,6 @@ export const getHorizontalBarVisTypeDefinition = ( tooltip: , }), }, - ]), + ], }, }); diff --git a/src/plugins/vis_type_xy/public/vis_types/line.tsx b/src/plugins/vis_type_xy/public/vis_types/line.tsx index 84e4070df495b..e0f83ce649d23 100644 --- a/src/plugins/vis_type_xy/public/vis_types/line.tsx +++ b/src/plugins/vis_type_xy/public/vis_types/line.tsx @@ -24,7 +24,6 @@ import { i18n } from '@kbn/i18n'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position, Fit } from '@elastic/charts'; -import { Schemas } from '../../../vis_default_editor/public'; import { AggGroupNames } from '../../../data/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../visualizations/public'; import { defaultCountLabel, LabelRotation } from '../../../charts/public'; @@ -139,7 +138,7 @@ export const getLineVisTypeDefinition = ( editorConfig: { collections: getConfigCollections(), optionTabs: getOptionTabs(showElasticChartsOptions), - schemas: new Schemas([ + schemas: [ { group: AggGroupNames.Metrics, name: 'metric', @@ -190,6 +189,6 @@ export const getLineVisTypeDefinition = ( tooltip: , }), }, - ]), + ], }, }); diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 3ddf0757ba4c8..854e04325b078 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -37,7 +37,14 @@ export { getSchemas as getVisSchemas } from './legacy/build_pipeline'; /** @public types */ export { VisualizationsSetup, VisualizationsStart }; export { VisGroups } from './vis_types'; -export type { VisTypeAlias, VisType, BaseVisTypeOptions, ReactVisTypeOptions } from './vis_types'; +export type { + VisTypeAlias, + VisType, + BaseVisTypeOptions, + ReactVisTypeOptions, + Schema, + ISchemas, +} from './vis_types'; export { VisParams, SerializedVis, SerializedVisData, VisData } from './vis'; export type VisualizeEmbeddableFactoryContract = PublicContract; export type VisualizeEmbeddableContract = PublicContract; diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index 66399352bea7d..7f60e6b1dc2ff 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -18,7 +18,8 @@ */ import { PluginInitializerContext } from '../../../core/public'; -import { VisualizationsSetup, VisualizationsStart } from './'; +import { Schema, VisualizationsSetup, VisualizationsStart } from './'; +import { Schemas } from './vis_types'; import { VisualizationsPlugin } from './plugin'; import { coreMock, applicationServiceMock } from '../../../core/public/mocks'; import { embeddablePluginMock } from '../../../plugins/embeddable/public/mocks'; @@ -86,6 +87,9 @@ const createInstance = async () => { }; }; +export const createMockedVisEditorSchemas = (schemas: Array>) => + new Schemas(schemas); + export const visualizationsPluginMock = { createSetupContract, createStartContract, diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index e52cd4d2b2d56..2088f52428aa7 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -18,9 +18,10 @@ */ import { defaultsDeep } from 'lodash'; -import { ISchemas } from 'src/plugins/vis_default_editor/public'; + import { VisParams } from '../types'; import { VisType, VisTypeOptions, VisGroups } from './types'; +import { Schemas } from './schemas'; interface CommonBaseVisTypeOptions extends Pick< @@ -102,6 +103,7 @@ export class BaseVisType implements VisType public readonly inspectorAdapters; public readonly toExpressionAst; public readonly getInfoMessage; + public readonly schemas; constructor(opts: BaseVisTypeOptions) { if (!opts.icon && !opts.image) { @@ -133,10 +135,8 @@ export class BaseVisType implements VisType this.inspectorAdapters = opts.inspectorAdapters; this.toExpressionAst = opts.toExpressionAst; this.getInfoMessage = opts.getInfoMessage; - } - public get schemas(): ISchemas { - return this.editorConfig?.schemas ?? []; + this.schemas = new Schemas(this.editorConfig?.schemas ?? []); } public get requiresSearch(): boolean { diff --git a/src/plugins/visualizations/public/vis_types/index.ts b/src/plugins/visualizations/public/vis_types/index.ts index a02ac82c8d122..43de5d1ecce53 100644 --- a/src/plugins/visualizations/public/vis_types/index.ts +++ b/src/plugins/visualizations/public/vis_types/index.ts @@ -18,7 +18,8 @@ */ export * from './types_service'; +export { Schemas } from './schemas'; export { VisGroups } from './types'; -export type { VisType } from './types'; +export type { VisType, ISchemas, Schema } from './types'; export type { BaseVisTypeOptions } from './base_vis_type'; export type { ReactVisTypeOptions } from './react_vis_type'; diff --git a/src/plugins/visualizations/public/vis_types/schemas.ts b/src/plugins/visualizations/public/vis_types/schemas.ts new file mode 100644 index 0000000000000..f19f57c32d546 --- /dev/null +++ b/src/plugins/visualizations/public/vis_types/schemas.ts @@ -0,0 +1,67 @@ +/* + * 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 _, { defaults } from 'lodash'; +import { AggGroupNames, AggParam } from '../../../data/public'; +import type { ISchemas, Schema } from './types'; + +/** @private **/ +export class Schemas implements ISchemas { + all: Schema[] = []; + [AggGroupNames.Buckets]: Schema[] = []; + [AggGroupNames.Metrics]: Schema[] = []; + + constructor(schemas: Array>) { + _(schemas || []) + .chain() + .map((schema) => { + if (!schema.name) throw new Error('all schema must have a unique name'); + + if (schema.name === 'split') { + schema.params = [ + { + name: 'row', + default: true, + }, + ] as AggParam[]; + } + + defaults(schema, { + min: 0, + max: Infinity, + group: AggGroupNames.Buckets, + title: schema.name, + aggFilter: '*', + editor: false, + params: [], + }); + + return schema as Schema; + }) + .tap((fullSchemas: Schema[]) => { + this.all = fullSchemas; + }) + .groupBy('group') + .forOwn((group, groupName) => { + // @ts-ignore + this[groupName] = group; + }) + .commit(); + } +} diff --git a/src/plugins/visualizations/public/vis_types/types.ts b/src/plugins/visualizations/public/vis_types/types.ts index 5b75d065118da..88a4dad106897 100644 --- a/src/plugins/visualizations/public/vis_types/types.ts +++ b/src/plugins/visualizations/public/vis_types/types.ts @@ -18,11 +18,10 @@ */ import { IconType } from '@elastic/eui'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { Adapters } from 'src/plugins/inspector'; -import { IndexPattern } from 'src/plugins/data/public'; import { VisEditorConstructor } from 'src/plugins/visualize/public'; -import { ISchemas } from 'src/plugins/vis_default_editor/public'; +import { IndexPattern, AggGroupNames, AggParam, AggGroupName } from '../../../data/public'; import { Vis, VisParams, VisToExpressionAst, VisualizationControllerConstructor } from '../types'; export interface VisTypeOptions { @@ -39,6 +38,29 @@ export enum VisGroups { AGGBASED = 'aggbased', } +export interface ISchemas { + [AggGroupNames.Buckets]: Schema[]; + [AggGroupNames.Metrics]: Schema[]; + all: Schema[]; +} + +export interface Schema { + aggFilter: string[]; + editor: boolean | string; + group: AggGroupName; + max: number; + min: number; + name: string; + params: AggParam[]; + title: string; + defaults: unknown; + hideCustomLabel?: boolean; + mustBeFirst?: boolean; + aggSettings?: any; + disabled?: boolean; + tooltip?: ReactNode; +} + /** * A visualization type representing one specific type of "classical" * visualizations (i.e. not Lens visualizations). diff --git a/test/functional/apps/timelion/_expression_typeahead.js b/test/functional/apps/timelion/_expression_typeahead.js index 5d834f1a055de..adad6f1c57acd 100644 --- a/test/functional/apps/timelion/_expression_typeahead.js +++ b/test/functional/apps/timelion/_expression_typeahead.js @@ -88,14 +88,14 @@ export default function ({ getPageObjects }) { const suggestions = await PageObjects.timelion.getSuggestionItemsText(); expect(suggestions.length).to.eql(51); expect(suggestions[0].includes('@message.raw')).to.eql(true); - await PageObjects.timelion.clickSuggestion(10); + await PageObjects.timelion.clickSuggestion(10, 2000); }); it('should show field suggestions for metric argument when index pattern set', async () => { await PageObjects.timelion.updateExpression(',metric'); await PageObjects.timelion.clickSuggestion(); await PageObjects.timelion.updateExpression('avg:'); - await PageObjects.timelion.clickSuggestion(); + await PageObjects.timelion.clickSuggestion(0, 2000); const suggestions = await PageObjects.timelion.getSuggestionItemsText(); expect(suggestions.length).to.eql(2); expect(suggestions[0].includes('avg:bytes')).to.eql(true); diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index de895918efbba..546f83e5b710a 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -17,6 +17,7 @@ * under the License. */ +import classNames from 'classnames'; import { FtrProviderContext } from '../ftr_provider_context'; export function FilterBarProvider({ getService, getPageObjects }: FtrProviderContext) { @@ -45,7 +46,14 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon const filterPinnedState = pinned ? 'pinned' : 'unpinned'; const filterNegatedState = negated ? 'filter-negated' : ''; return testSubjects.exists( - `filter filter-${filterActivationState} filter-key-${key} filter-value-${value} filter-${filterPinnedState} ${filterNegatedState}`, + classNames( + 'filter', + `filter-${filterActivationState}`, + key !== '' && `filter-key-${key}`, + value !== '' && `filter-value-${value}`, + `filter-${filterPinnedState}`, + filterNegatedState + ), { allowHidden: true, } diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index afc8951f121ea..2c71e75994a4a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -109,7 +109,7 @@ export function TimeseriesChart({ }} onPointerUpdate={setPointerEvent} externalPointerEvents={{ - tooltip: { visible: true, placement: Placement.Bottom }, + tooltip: { visible: true, placement: Placement.Right }, }} showLegend showLegendExtra diff --git a/x-pack/plugins/grokdebugger/jest.config.js b/x-pack/plugins/grokdebugger/jest.config.js new file mode 100644 index 0000000000000..bf43870b5ba65 --- /dev/null +++ b/x-pack/plugins/grokdebugger/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/grokdebugger'], +}; diff --git a/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.test.js similarity index 83% rename from x-pack/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.test.js index 2e0be6001f8ca..0644a797da8bd 100644 --- a/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js +++ b/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.test.js @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { GrokdebuggerRequest } from '../grokdebugger_request'; +import { GrokdebuggerRequest } from './grokdebugger_request'; // FAILING: https://github.com/elastic/kibana/issues/51372 describe.skip('grokdebugger_request', () => { @@ -24,18 +23,18 @@ describe.skip('grokdebugger_request', () => { describe('fromDownstreamJSON factory method', () => { it('returns correct GrokdebuggerRequest instance from downstreamRequest', () => { const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(downstreamRequest); - expect(grokdebuggerRequest.rawEvent).to.eql(downstreamRequest.rawEvent); - expect(grokdebuggerRequest.pattern).to.eql(downstreamRequest.pattern); - expect(grokdebuggerRequest.customPatterns).to.eql({}); + expect(grokdebuggerRequest.rawEvent).toEqual(downstreamRequest.rawEvent); + expect(grokdebuggerRequest.pattern).toEqual(downstreamRequest.pattern); + expect(grokdebuggerRequest.customPatterns).toEqual({}); }); it('returns correct GrokdebuggerRequest instance from downstreamRequest when custom patterns are specified', () => { const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON( downstreamRequestWithCustomPatterns ); - expect(grokdebuggerRequest.rawEvent).to.eql(downstreamRequest.rawEvent); - expect(grokdebuggerRequest.pattern).to.eql(downstreamRequest.pattern); - expect(grokdebuggerRequest.customPatterns).to.eql('%{FOO:bar}'); + expect(grokdebuggerRequest.rawEvent).toEqual(downstreamRequest.rawEvent); + expect(grokdebuggerRequest.pattern).toEqual(downstreamRequest.pattern); + expect(grokdebuggerRequest.customPatterns).toEqual('%{FOO:bar}'); }); }); @@ -67,7 +66,7 @@ describe.skip('grokdebugger_request', () => { }; const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(downstreamRequest); const upstreamJson = grokdebuggerRequest.upstreamJSON; - expect(upstreamJson).to.eql(expectedUpstreamJSON); + expect(upstreamJson).toEqual(expectedUpstreamJSON); }); it('returns the upstream simulate JSON request when custom patterns are specified', () => { @@ -99,7 +98,7 @@ describe.skip('grokdebugger_request', () => { downstreamRequestWithCustomPatterns ); const upstreamJson = grokdebuggerRequest.upstreamJSON; - expect(upstreamJson).to.eql(expectedUpstreamJSON); + expect(upstreamJson).toEqual(expectedUpstreamJSON); }); }); }); diff --git a/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.test.js similarity index 85% rename from x-pack/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.test.js index 3dde3244ed19b..de550b3f9bccd 100644 --- a/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js +++ b/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.test.js @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { GrokdebuggerResponse } from '../grokdebugger_response'; +import { GrokdebuggerResponse } from './grokdebugger_response'; describe('grokdebugger_response', () => { describe('GrokdebuggerResponse', () => { @@ -37,8 +36,8 @@ describe('grokdebugger_response', () => { client: '55.3.244.1', }; const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON(upstreamJson); - expect(grokdebuggerResponse.structuredEvent).to.eql(expectedStructuredEvent); - expect(grokdebuggerResponse.error).to.eql({}); + expect(grokdebuggerResponse.structuredEvent).toEqual(expectedStructuredEvent); + expect(grokdebuggerResponse.error).toEqual({}); }); it('returns correct GrokdebuggerResponse instance when there are valid grok parse errors', () => { @@ -61,8 +60,8 @@ describe('grokdebugger_response', () => { ], }; const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON(upstreamJson); - expect(grokdebuggerResponse.structuredEvent).to.eql({}); - expect(grokdebuggerResponse.error).to.be( + expect(grokdebuggerResponse.structuredEvent).toEqual({}); + expect(grokdebuggerResponse.error).toBe( 'Provided Grok patterns do not match data in the input' ); }); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx index f97a8d1c3a872..7651d20249c93 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx @@ -33,7 +33,10 @@ export const DateContent: FC = ({ config }) => { { function: 'earliest', display: ( - + ), value: formatDate(earliest, TIME_FORMAT), }, @@ -41,8 +44,8 @@ export const DateContent: FC = ({ config }) => { function: 'latest', display: ( ), value: formatDate(latest, TIME_FORMAT), diff --git a/x-pack/plugins/security_solution/cypress/cypress.json b/x-pack/plugins/security_solution/cypress/cypress.json index 6feb9d794740d..6a9a240af5873 100644 --- a/x-pack/plugins/security_solution/cypress/cypress.json +++ b/x-pack/plugins/security_solution/cypress/cypress.json @@ -2,6 +2,7 @@ "baseUrl": "http://localhost:5601", "defaultCommandTimeout": 60000, "execTimeout": 120000, + "pageLoadTimeout": 120000, "nodeVersion": "system", "retries": { "runMode": 2 diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts index 4bf54963a5322..5e501d2d51627 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts @@ -39,7 +39,7 @@ describe('Detections > Callouts indicating read-only access to resources', () => const RULES_CALLOUT = 'read-only-access-to-rules'; before(() => { - // First, we have to open the app on behalf of a priviledged user in order to initialize it. + // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. cleanKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts index 9eb2127acb446..125848c85a84a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts @@ -31,14 +31,13 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants'; import { DETECTIONS_URL } from '../urls/navigation'; -import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../objects/rule'; describe('Alerts detection rules', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 2de8069870848..dff39567ecacd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -112,35 +112,39 @@ describe('Custom detection rules creation', () => { const expectedMitre = formatMitreAttackDescription(newRule.mitre); const expectedNumberOfRules = 1; - const rule = { ...newRule }; - beforeEach(() => { cleanKibana(); createTimeline(newRule.timeline).then((response) => { - rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; + cy.wrap({ + ...newRule, + timeline: { + ...newRule.timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }).as('rule'); }); }); - it('Creates and activates a new rule', () => { + it('Creates and activates a new rule', function () { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); - fillDefineCustomRuleWithImportedQueryAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); + fillDefineCustomRuleWithImportedQueryAndContinue(this.rule); + fillAboutRuleAndContinue(this.rule); + fillScheduleRuleAndContinue(this.rule); // expect define step to repopulate cy.get(DEFINE_EDIT_BUTTON).click(); - cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery); + cy.get(CUSTOM_QUERY_INPUT).should('have.value', this.rule.customQuery); cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist'); // expect about step to populate cy.get(ABOUT_EDIT_BUTTON).click(); - cy.get(RULE_NAME_INPUT).invoke('val').should('eql', rule.name); + cy.get(RULE_NAME_INPUT).invoke('val').should('eql', this.rule.name); cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true }); cy.get(ABOUT_CONTINUE_BTN).should('not.exist'); @@ -160,18 +164,18 @@ describe('Custom detection rules creation', () => { cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.riskScore); - cy.get(SEVERITY).should('have.text', rule.severity); + cy.get(RULE_NAME).should('have.text', this.rule.name); + cy.get(RISK_SCORE).should('have.text', this.rule.riskScore); + cy.get(SEVERITY).should('have.text', this.rule.severity); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); goToRuleDetails(); - cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(RULE_NAME_HEADER).should('have.text', `${this.rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', this.rule.description); cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); + getDetails(SEVERITY_DETAILS).should('have.text', this.rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', this.rule.riskScore); getDetails(REFERENCE_URLS_DETAILS).should((details) => { expect(removeExternalLinkText(details.text())).equal(expectedUrls); }); @@ -185,7 +189,7 @@ describe('Custom detection rules creation', () => { cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); }); @@ -203,12 +207,12 @@ describe('Custom detection rules creation', () => { waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS).invoke('text').then(parseFloat).should('be.above', 0); - cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); + cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.gte(1)); + cy.get(ALERT_RULE_NAME).first().should('have.text', this.rule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'query'); - cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + cy.get(ALERT_RULE_SEVERITY).first().should('have.text', this.rule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', this.rule.riskScore); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index 6567ee07c4e3a..b4d39385cd411 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -77,7 +77,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe.skip('Detection rules, EQL', () => { +describe('Detection rules, EQL', () => { const expectedUrls = eqlRule.referenceUrls.join(''); const expectedFalsePositives = eqlRule.falsePositivesExamples.join(''); const expectedTags = eqlRule.tags.join(''); @@ -85,16 +85,20 @@ describe.skip('Detection rules, EQL', () => { const expectedNumberOfRules = 1; const expectedNumberOfAlerts = 7; - const rule = { ...eqlRule }; - beforeEach(() => { cleanKibana(); createTimeline(eqlRule.timeline).then((response) => { - rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; + cy.wrap({ + ...eqlRule, + timeline: { + ...eqlRule.timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }).as('rule'); }); }); - it('Creates and activates a new EQL rule', () => { + it('Creates and activates a new EQL rule', function () { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); @@ -102,9 +106,9 @@ describe.skip('Detection rules, EQL', () => { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); selectEqlRuleType(); - fillDefineEqlRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); + fillDefineEqlRuleAndContinue(this.rule); + fillAboutRuleAndContinue(this.rule); + fillScheduleRuleAndContinue(this.rule); createAndActivateRule(); cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); @@ -121,18 +125,18 @@ describe.skip('Detection rules, EQL', () => { cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.riskScore); - cy.get(SEVERITY).should('have.text', rule.severity); + cy.get(RULE_NAME).should('have.text', this.rule.name); + cy.get(RISK_SCORE).should('have.text', this.rule.riskScore); + cy.get(SEVERITY).should('have.text', this.rule.severity); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); goToRuleDetails(); - cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(RULE_NAME_HEADER).should('have.text', `${this.rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', this.rule.description); cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); + getDetails(SEVERITY_DETAILS).should('have.text', this.rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', this.rule.riskScore); getDetails(REFERENCE_URLS_DETAILS).should((details) => { expect(removeExternalLinkText(details.text())).equal(expectedUrls); }); @@ -146,18 +150,18 @@ describe.skip('Detection rules, EQL', () => { cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Event Correlation'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); }); cy.get(SCHEDULE_DETAILS).within(() => { getDetails(RUNS_EVERY_DETAILS).should( 'have.text', - `${rule.runsEvery.interval}${rule.runsEvery.type}` + `${this.rule.runsEvery.interval}${this.rule.runsEvery.type}` ); getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( 'have.text', - `${rule.lookBack.interval}${rule.lookBack.type}` + `${this.rule.lookBack.interval}${this.rule.lookBack.type}` ); }); @@ -165,27 +169,32 @@ describe.skip('Detection rules, EQL', () => { waitForAlertsToPopulate(); cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); - cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); + cy.get(ALERT_RULE_NAME).first().should('have.text', this.rule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'eql'); - cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + cy.get(ALERT_RULE_SEVERITY).first().should('have.text', this.rule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', this.rule.riskScore); }); }); -describe.skip('Detection rules, sequence EQL', () => { +describe('Detection rules, sequence EQL', () => { const expectedNumberOfRules = 1; const expectedNumberOfSequenceAlerts = 1; - const rule = { ...eqlSequenceRule }; beforeEach(() => { cleanKibana(); createTimeline(eqlSequenceRule.timeline).then((response) => { - rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; + cy.wrap({ + ...eqlSequenceRule, + timeline: { + ...eqlSequenceRule.timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }).as('rule'); }); }); - it('Creates and activates a new EQL rule with a sequence', () => { + it('Creates and activates a new EQL rule with a sequence', function () { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); @@ -193,9 +202,9 @@ describe.skip('Detection rules, sequence EQL', () => { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); selectEqlRuleType(); - fillDefineEqlRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); + fillDefineEqlRuleAndContinue(this.rule); + fillAboutRuleAndContinue(this.rule); + fillScheduleRuleAndContinue(this.rule); createAndActivateRule(); cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); @@ -213,10 +222,10 @@ describe.skip('Detection rules, sequence EQL', () => { waitForAlertsToPopulate(); cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfSequenceAlerts); - cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); + cy.get(ALERT_RULE_NAME).first().should('have.text', this.rule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'eql'); - cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + cy.get(ALERT_RULE_SEVERITY).first().should('have.text', this.rule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', this.rule.riskScore); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index 0f5ce9c47a439..f33ecd3f49a8c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -17,8 +17,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe.skip('Export rules', () => { - let ruleResponse: Cypress.Response; +describe('Export rules', () => { beforeEach(() => { cleanKibana(); cy.intercept( @@ -28,16 +27,14 @@ describe.skip('Export rules', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); - createCustomRule(newRule).then((response) => { - ruleResponse = response; - }); + createCustomRule(newRule).as('ruleResponse'); }); - it('Exports a custom rule', () => { + it('Exports a custom rule', function () { goToManageAlertsDetectionRules(); exportFirstRule(); cy.wait('@export').then(({ response }) => { - cy.wrap(response!.body).should('eql', expectedExportedRule(ruleResponse)); + cy.wrap(response!.body).should('eql', expectedExportedRule(this.ruleResponse)); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts index baefcba945447..0813b51cd84c3 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts @@ -88,7 +88,7 @@ describe.skip('Detection rules, machine learning', () => { fillScheduleRuleAndContinue(machineLearningRule); createAndActivateRule(); - cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)'); + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); changeToThreeHundredRowsPerPage(); waitForRulesToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index c641d572f515c..9c7074f48cf96 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -5,7 +5,7 @@ */ import { formatMitreAttackDescription } from '../helpers/rules'; -import { indexPatterns, newOverrideRule, severitiesOverride } from '../objects/rule'; +import { indexPatterns, newOverrideRule, severitiesOverride, OverrideRule } from '../objects/rule'; import { NUMBER_OF_ALERTS, @@ -89,25 +89,29 @@ describe('Detection rules, override', () => { const expectedTags = newOverrideRule.tags.join(''); const expectedMitre = formatMitreAttackDescription(newOverrideRule.mitre); - const rule = { ...newOverrideRule }; - beforeEach(() => { cleanKibana(); createTimeline(newOverrideRule.timeline).then((response) => { - rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; + cy.wrap({ + ...newOverrideRule, + timeline: { + ...newOverrideRule.timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }).as('rule'); }); }); - it('Creates and activates a new custom rule with override option', () => { + it('Creates and activates a new custom rule with override option', function () { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); - fillDefineCustomRuleWithImportedQueryAndContinue(rule); - fillAboutRuleWithOverrideAndContinue(rule); - fillScheduleRuleAndContinue(rule); + fillDefineCustomRuleWithImportedQueryAndContinue(this.rule); + fillAboutRuleWithOverrideAndContinue(this.rule); + fillScheduleRuleAndContinue(this.rule); createAndActivateRule(); cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); @@ -125,23 +129,23 @@ describe('Detection rules, override', () => { cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.riskScore); - cy.get(SEVERITY).should('have.text', rule.severity); + cy.get(RULE_NAME).should('have.text', this.rule.name); + cy.get(RISK_SCORE).should('have.text', this.rule.riskScore); + cy.get(SEVERITY).should('have.text', this.rule.severity); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); goToRuleDetails(); - cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(RULE_NAME_HEADER).should('have.text', `${this.rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', this.rule.description); cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); + getDetails(SEVERITY_DETAILS).should('have.text', this.rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', this.rule.riskScore); getDetails(RISK_SCORE_OVERRIDE_DETAILS).should( 'have.text', - `${rule.riskOverride}signal.rule.risk_score` + `${this.rule.riskOverride}signal.rule.risk_score` ); - getDetails(RULE_NAME_OVERRIDE_DETAILS).should('have.text', rule.nameOverride); + getDetails(RULE_NAME_OVERRIDE_DETAILS).should('have.text', this.rule.nameOverride); getDetails(REFERENCE_URLS_DETAILS).should((details) => { expect(removeExternalLinkText(details.text())).equal(expectedUrls); }); @@ -150,11 +154,11 @@ describe('Detection rules, override', () => { expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); }); getDetails(TAGS_DETAILS).should('have.text', expectedTags); - getDetails(TIMESTAMP_OVERRIDE_DETAILS).should('have.text', rule.timestampOverride); + getDetails(TIMESTAMP_OVERRIDE_DETAILS).should('have.text', this.rule.timestampOverride); cy.contains(DETAILS_TITLE, 'Severity override') .invoke('index', DETAILS_TITLE) // get index relative to other titles, not all siblings .then((severityOverrideIndex) => { - rule.severityOverride.forEach((severity, i) => { + (this.rule as OverrideRule).severityOverride.forEach((severity, i) => { cy.get(DETAILS_DESCRIPTION) .eq(severityOverrideIndex + i) .should( @@ -168,25 +172,25 @@ describe('Detection rules, override', () => { cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); }); cy.get(SCHEDULE_DETAILS).within(() => { getDetails(RUNS_EVERY_DETAILS).should( 'have.text', - `${rule.runsEvery.interval}${rule.runsEvery.type}` + `${this.rule.runsEvery.interval}${this.rule.runsEvery.type}` ); getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( 'have.text', - `${rule.lookBack.interval}${rule.lookBack.type}` + `${this.rule.lookBack.interval}${this.rule.lookBack.type}` ); }); waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS).invoke('text').then(parseFloat).should('be.above', 0); + cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.gte(1)); cy.get(ALERT_RULE_NAME).first().should('have.text', 'auditbeat'); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'query'); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index 058bac6258ffc..96d7c3d5d5a84 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -169,7 +169,7 @@ describe.skip('Detection rules, threshold', () => { waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS).invoke('text').then(parseFloat).should('be.below', 100); + cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.lt(100)); cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threshold'); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index 18325401d9abc..9fa9d83ec85ea 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -26,7 +26,7 @@ import { import { CASE_DETAILS_DESCRIPTION, CASE_DETAILS_PAGE_TITLE, - CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN, + // CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN, CASE_DETAILS_STATUS, CASE_DETAILS_TAGS, CASE_DETAILS_USER_ACTION_DESCRIPTION_USERNAME, @@ -52,20 +52,26 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { CASES_URL } from '../urls/navigation'; describe('Cases', () => { - const mycase = { ...case1 }; - beforeEach(() => { cleanKibana(); - createTimeline(case1.timeline).then((response) => { - mycase.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; - }); + createTimeline(case1.timeline).then((response) => + cy + .wrap({ + ...case1, + timeline: { + ...case1.timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }) + .as('mycase') + ); }); - it('Creates a new case with timeline and opens the timeline', () => { + it('Creates a new case with timeline and opens the timeline', function () { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); - fillCasesMandatoryfields(mycase); - attachTimeline(mycase); + fillCasesMandatoryfields(this.mycase); + attachTimeline(this.mycase); createCase(); backToCases(); @@ -76,9 +82,9 @@ describe('Cases', () => { cy.get(ALL_CASES_OPEN_CASES_COUNT).should('have.text', 'Open (1)'); cy.get(ALL_CASES_REPORTERS_COUNT).should('have.text', 'Reporter1'); cy.get(ALL_CASES_TAGS_COUNT).should('have.text', 'Tags2'); - cy.get(ALL_CASES_NAME).should('have.text', mycase.name); - cy.get(ALL_CASES_REPORTER).should('have.text', mycase.reporter); - mycase.tags.forEach((tag, index) => { + cy.get(ALL_CASES_NAME).should('have.text', this.mycase.name); + cy.get(ALL_CASES_REPORTER).should('have.text', this.mycase.reporter); + (this.mycase as typeof case1).tags.forEach((tag, index) => { cy.get(ALL_CASES_TAGS(index)).should('have.text', tag); }); cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0'); @@ -89,24 +95,24 @@ describe('Cases', () => { goToCaseDetails(); - const expectedTags = mycase.tags.join(''); - cy.get(CASE_DETAILS_PAGE_TITLE).should('have.text', mycase.name); + const expectedTags = this.mycase.tags.join(''); + cy.get(CASE_DETAILS_PAGE_TITLE).should('have.text', this.mycase.name); cy.get(CASE_DETAILS_STATUS).should('have.text', 'Open'); - cy.get(CASE_DETAILS_USER_ACTION_DESCRIPTION_USERNAME).should('have.text', mycase.reporter); + cy.get(CASE_DETAILS_USER_ACTION_DESCRIPTION_USERNAME).should('have.text', this.mycase.reporter); cy.get(CASE_DETAILS_USER_ACTION_DESCRIPTION_EVENT).should('have.text', 'added description'); cy.get(CASE_DETAILS_DESCRIPTION).should( 'have.text', - `${mycase.description} ${mycase.timeline.title}` + `${this.mycase.description} ${this.mycase.timeline.title}` ); - cy.get(CASE_DETAILS_USERNAMES).eq(REPORTER).should('have.text', mycase.reporter); - cy.get(CASE_DETAILS_USERNAMES).eq(PARTICIPANTS).should('have.text', mycase.reporter); + cy.get(CASE_DETAILS_USERNAMES).eq(REPORTER).should('have.text', this.mycase.reporter); + cy.get(CASE_DETAILS_USERNAMES).eq(PARTICIPANTS).should('have.text', this.mycase.reporter); cy.get(CASE_DETAILS_TAGS).should('have.text', expectedTags); - cy.get(CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN).should('have.attr', 'disabled'); + // cy.get(CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN).should('have.attr', 'disabled'); openCaseTimeline(); - cy.get(TIMELINE_TITLE).contains(mycase.timeline.title); - cy.get(TIMELINE_DESCRIPTION).contains(mycase.timeline.description); - cy.get(TIMELINE_QUERY).invoke('text').should('eq', mycase.timeline.query); + cy.get(TIMELINE_TITLE).contains(this.mycase.timeline.title); + cy.get(TIMELINE_DESCRIPTION).contains(this.mycase.timeline.description); + cy.get(TIMELINE_QUERY).should('have.text', this.mycase.timeline.query); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index 4e34dcac1873d..721ce277338f6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -44,7 +44,7 @@ const defaultHeadersInDefaultEcsCategory = [ { id: 'destination.ip' }, ]; -describe.skip('Events Viewer', () => { +describe('Events Viewer', () => { context('Fields rendering', () => { before(() => { cleanKibana(); @@ -118,7 +118,7 @@ describe.skip('Events Viewer', () => { }); }); - context('Events behaviour', () => { + context('Events behavior', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts index 0d3890a5292e4..ecb6212b0a03a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts @@ -32,36 +32,34 @@ describe('ml conditional links', () => { it('sets the KQL from a single IP with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")'); + cy.get(KQL_INPUT).should( + 'have.text', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); }); it('sets the KQL from a multiple IPs with a null for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should( - 'eq', - '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2"))' - ); + cy.get(KQL_INPUT).should( + 'have.text', + '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2"))' + ); }); it('sets the KQL from a multiple IPs with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should( - 'eq', - '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2")) and ((process.name: "conhost.exe" or process.name: "sc.exe"))' - ); + cy.get(KQL_INPUT).should( + 'have.text', + '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2")) and ((process.name: "conhost.exe" or process.name: "sc.exe"))' + ); }); it('sets the KQL from a $ip$ with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")'); + cy.get(KQL_INPUT).should( + 'have.text', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); }); it('sets the KQL from a single host name with a value for query', () => { @@ -73,26 +71,26 @@ describe('ml conditional links', () => { it('sets the KQL from a multiple host names with null for query', () => { loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should('eq', '(host.name: "siem-windows" or host.name: "siem-suricata")'); + cy.get(KQL_INPUT).should( + 'have.text', + '(host.name: "siem-windows" or host.name: "siem-suricata")' + ); }); it('sets the KQL from a multiple host names with a value for query', () => { loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should( - 'eq', - '(host.name: "siem-windows" or host.name: "siem-suricata") and ((process.name: "conhost.exe" or process.name: "sc.exe"))' - ); + cy.get(KQL_INPUT).should( + 'have.text', + '(host.name: "siem-windows" or host.name: "siem-suricata") and ((process.name: "conhost.exe" or process.name: "sc.exe"))' + ); }); it('sets the KQL from a undefined/null host name but with a value for query', () => { loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery); - cy.get(KQL_INPUT) - .invoke('text') - .should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")'); + cy.get(KQL_INPUT).should( + 'have.text', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); }); it('redirects from a single IP with a null for the query', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index f72559b9f21e6..0b1ee9f84f910 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -26,7 +26,7 @@ describe('Overview Page', () => { expandHostStats(); HOST_STATS.forEach((stat) => { - cy.get(stat.domId).invoke('text').should('eq', stat.value); + cy.get(stat.domId).should('have.text', stat.value); }); }); @@ -36,7 +36,7 @@ describe('Overview Page', () => { expandNetworkStats(); NETWORK_STATS.forEach((stat) => { - cy.get(stat.domId).invoke('text').should('eq', stat.value); + cy.get(stat.domId).should('have.text', stat.value); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts index aa126e2f33c90..96007ca0326d1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts @@ -34,8 +34,10 @@ describe.skip('Sourcerer', () => { }); beforeEach(() => { + cy.clearLocalStorage(); loginAndWaitForPage(HOSTS_URL); }); + describe('Default scope', () => { it('has SIEM index patterns selected on initial load', () => { openSourcerer(); @@ -46,6 +48,7 @@ describe.skip('Sourcerer', () => { openSourcerer(); isSourcererOptions([`metrics-*`, `logs-*`]); }); + it('selected KIP gets added to sourcerer', () => { setSourcererOption(`metrics-*`); openSourcerer(); @@ -69,10 +72,12 @@ describe.skip('Sourcerer', () => { isNotSourcererSelection(`metrics-*`); }); }); + describe('Timeline scope', () => { const alertPatterns = ['.siem-signals-default']; const rawPatterns = ['auditbeat-*']; const allPatterns = [...alertPatterns, ...rawPatterns]; + it('Radio buttons select correct sourcerer patterns', () => { openTimelineUsingToggle(); openSourcerer('timeline'); @@ -84,6 +89,7 @@ describe.skip('Sourcerer', () => { alertPatterns.forEach((ss) => isSourcererSelection(ss, 'timeline')); rawPatterns.forEach((ss) => isNotSourcererSelection(ss, 'timeline')); }); + it('Adding an option results in the custom radio becoming active', () => { openTimelineUsingToggle(); openSourcerer('timeline'); @@ -94,17 +100,13 @@ describe.skip('Sourcerer', () => { openSourcerer('timeline'); isCustomRadio(); }); - it.skip('Selected index patterns are properly queried', () => { + + it('Selected index patterns are properly queried', () => { openTimelineUsingToggle(); populateTimeline(); openSourcerer('timeline'); deselectSourcererOptions(rawPatterns, 'timeline'); - cy.get(SERVER_SIDE_EVENT_COUNT) - .invoke('text') - .then((strCount) => { - const intCount = +strCount; - cy.wrap(intCount).should('eq', 0); - }); + cy.get(SERVER_SIDE_EVENT_COUNT).should(($count) => expect(+$count.text()).to.eql(0)); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts index a0051eee0a22e..56b2ef00169dc 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts @@ -20,66 +20,58 @@ import { createCase } from '../tasks/api_calls/cases'; // https://github.com/elastic/kibana/issues/86959 describe.skip('attach timeline to case', () => { - const myTimeline = { ...timeline }; - context('without cases created', () => { - before(() => { + beforeEach(() => { cleanKibana(); - createTimeline(timeline).then((response) => { - myTimeline.id = response.body.data.persistTimeline.timeline.savedObjectId; - }); + createTimeline(timeline).then((response) => + cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline') + ); }); - it('attach timeline to a new case', () => { - loginAndWaitForTimeline(myTimeline.id!); + it('attach timeline to a new case', function () { + loginAndWaitForTimeline(this.myTimeline.savedObjectId); attachTimelineToNewCase(); cy.location('origin').then((origin) => { cy.get(DESCRIPTION_INPUT).should( 'have.text', - `[${myTimeline.title}](${origin}/app/security/timelines?timeline=(id:%27${myTimeline.id}%27,isOpen:!t))` + `[${this.myTimeline.title}](${origin}/app/security/timelines?timeline=(id:%27${this.myTimeline.savedObjectId}%27,isOpen:!t))` ); }); }); - it('attach timeline to an existing case with no case', () => { - loginAndWaitForTimeline(myTimeline.id!); + it('attach timeline to an existing case with no case', function () { + loginAndWaitForTimeline(this.myTimeline.savedObjectId); attachTimelineToExistingCase(); addNewCase(); cy.location('origin').then((origin) => { cy.get(DESCRIPTION_INPUT).should( 'have.text', - `[${ - myTimeline.title - }](${origin}/app/security/timelines?timeline=(id:%27${myTimeline.id!}%27,isOpen:!t))` + `[${this.myTimeline.title}](${origin}/app/security/timelines?timeline=(id:%27${this.myTimeline.savedObjectId}%27,isOpen:!t))` ); }); }); }); context('with cases created', () => { - let timelineId: string; - let caseId: string; before(() => { cleanKibana(); - createTimeline(timeline).then((response) => { - timelineId = response.body.data.persistTimeline.timeline.savedObjectId; - }); - createCase(case1).then((response) => { - caseId = response.body.id; - }); + createTimeline(timeline).then((response) => + cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId') + ); + createCase(case1).then((response) => cy.wrap(response.body.id).as('caseId')); }); - it('attach timeline to an existing case', () => { - loginAndWaitForTimeline(timelineId); + it('attach timeline to an existing case', function () { + loginAndWaitForTimeline(this.timelineId); attachTimelineToExistingCase(); - selectCase(caseId); + selectCase(this.caseId); cy.location('origin').then((origin) => { cy.get(ADD_COMMENT_INPUT).should( 'have.text', - `[${timeline.title}](${origin}/app/security/timelines?timeline=(id:%27${timelineId}%27,isOpen:!t))` + `[${timeline.title}](${origin}/app/security/timelines?timeline=(id:%27${this.timelineId}%27,isOpen:!t))` ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts index a926a5ac4938a..cacf2802b6d71 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts @@ -8,6 +8,7 @@ import { timeline } from '../objects/timeline'; import { FAVORITE_TIMELINE, LOCKED_ICON, + UNLOCKED_ICON, NOTES_TAB_BUTTON, NOTES_TEXT, // NOTES_COUNT, @@ -47,8 +48,6 @@ import { openTimeline } from '../tasks/timelines'; import { OVERVIEW_URL } from '../urls/navigation'; describe('Timelines', () => { - let timelineId: string; - beforeEach(() => { cleanKibana(); }); @@ -70,7 +69,7 @@ describe('Timelines', () => { addNameToTimeline(timeline.title); cy.wait('@timeline').then(({ response }) => { - timelineId = response!.body.data.persistTimeline.timeline.savedObjectId; + const timelineId = response!.body.data.persistTimeline.timeline.savedObjectId; addDescriptionToTimeline(timeline.description); addNotesToTimeline(timeline.notes); @@ -96,6 +95,7 @@ describe('Timelines', () => { cy.get(PIN_EVENT) .should('have.attr', 'aria-label') .and('match', /Unpin the event in row 2/); + cy.get(UNLOCKED_ICON).should('be.visible'); cy.get(NOTES_TAB_BUTTON).click(); cy.get(NOTES_TEXT_AREA).should('exist'); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts index a103586e007e4..32ffb01b8ff55 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts @@ -25,9 +25,7 @@ import { closeTimeline, createNewTimeline } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; import { cleanKibana } from '../tasks/common'; -// FLAKY: https://github.com/elastic/kibana/issues/85098 -// FLAKY: https://github.com/elastic/kibana/issues/62060 -describe.skip('timeline data providers', () => { +describe('timeline data providers', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index 1d0256dbfbdc9..f5091dd893446 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -20,24 +20,21 @@ describe.skip('persistent timeline', () => { loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); + cy.get(DRAGGABLE_HEADER).then((header) => + cy.wrap(header.length - 1).as('expectedNumberOfTimelineColumns') + ); }); - it('persist the deletion of a column', () => { - cy.get(DRAGGABLE_HEADER).then((header) => { - const currentNumberOfTimelineColumns = header.length; - const expectedNumberOfTimelineColumns = currentNumberOfTimelineColumns - 1; + it('persist the deletion of a column', function () { + cy.get(DRAGGABLE_HEADER).eq(TABLE_COLUMN_EVENTS_MESSAGE).should('have.text', 'message'); + removeColumn(TABLE_COLUMN_EVENTS_MESSAGE); - cy.wrap(header).eq(TABLE_COLUMN_EVENTS_MESSAGE).invoke('text').should('equal', 'message'); - removeColumn(TABLE_COLUMN_EVENTS_MESSAGE); + cy.get(DRAGGABLE_HEADER).should('have.length', this.expectedNumberOfTimelineColumns); - cy.get(DRAGGABLE_HEADER).should('have.length', expectedNumberOfTimelineColumns); - - reload(waitsForEventsToBeLoaded); + reload(); + waitsForEventsToBeLoaded(); - cy.get(DRAGGABLE_HEADER).should('have.length', expectedNumberOfTimelineColumns); - cy.get(DRAGGABLE_HEADER).each(($el) => { - expect($el.text()).not.equal('message'); - }); - }); + cy.get(DRAGGABLE_HEADER).should('have.length', this.expectedNumberOfTimelineColumns); + cy.get(DRAGGABLE_HEADER).each(($el) => expect($el.text()).not.equal('message')); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts index 52274329034b1..54a717e7a29e7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts @@ -13,7 +13,7 @@ import { executeTimelineKQL } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; -describe.skip('timeline search or filter KQL bar', () => { +describe('timeline search or filter KQL bar', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); @@ -24,11 +24,6 @@ describe.skip('timeline search or filter KQL bar', () => { openTimelineUsingToggle(); executeTimelineKQL(hostExistsQuery); - cy.get(SERVER_SIDE_EVENT_COUNT) - .invoke('text') - .then((strCount) => { - const intCount = +strCount; - cy.wrap(intCount).should('be.above', 0); - }); + cy.get(SERVER_SIDE_EVENT_COUNT).should(($count) => expect(+$count.text()).to.be.gt(0)); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts index f2af37c939d02..cc526b53033a5 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts @@ -16,24 +16,24 @@ import { createTimelineTemplate } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { - let templateResponse: Cypress.Response; - let templateId: string; - beforeEach(() => { cleanKibana(); cy.intercept('POST', 'api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimelineTemplate(timelineTemplate).then((response) => { - templateResponse = response; - templateId = response.body.data.persistTimeline.timeline.savedObjectId; + cy.wrap(response).as('templateResponse'); + cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('templateId'); }); }); - it('Exports a custom timeline template', () => { + it('Exports a custom timeline template', function () { loginAndWaitForPageWithoutDateRange(TIMELINE_TEMPLATES_URL); - exportTimeline(templateId!); + exportTimeline(this.templateId); cy.wait('@export').then(({ response }) => { - cy.wrap(response!.body).should('eql', expectedExportedTimelineTemplate(templateResponse)); + cy.wrap(response!.body).should( + 'eql', + expectedExportedTimelineTemplate(this.templateResponse) + ); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts index a75074baeef54..cba9cfb2579f1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -13,25 +13,23 @@ import { expectedExportedTimeline, timeline } from '../objects/timeline'; import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { - let timelineResponse: Cypress.Response; - let timelineId: string; beforeEach(() => { cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimeline(timeline).then((response) => { - timelineResponse = response; - timelineId = response.body.data.persistTimeline.timeline.savedObjectId; + cy.wrap(response).as('timelineResponse'); + cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId'); }); }); - it('Exports a custom timeline', () => { + it('Exports a custom timeline', function () { loginAndWaitForPageWithoutDateRange(TIMELINES_URL); waitForTimelinesPanelToBeLoaded(); - exportTimeline(timelineId); + exportTimeline(this.timelineId); cy.wait('@export').then(({ response }) => { cy.wrap(response!.statusCode).should('eql', 200); - cy.wrap(response!.body).should('eql', expectedExportedTimeline(timelineResponse)); + cy.wrap(response!.body).should('eql', expectedExportedTimeline(this.timelineResponse)); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index a13ae62eb7f84..18f14e8d8b12f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -49,9 +49,8 @@ const ABSOLUTE_DATE = { startTimeTimeline: '2019-08-02T20:03:29.186Z', }; -// FLAKY: https://github.com/elastic/kibana/issues/61612 -describe.skip('url state', () => { - before(() => { +describe('url state', () => { + beforeEach(() => { cleanKibana(); }); @@ -142,12 +141,12 @@ describe.skip('url state', () => { it('sets kql on network page', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlNetworkNetwork); - cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"'); + cy.get(KQL_INPUT).should('have.text', 'source.ip: "10.142.0.9"'); }); it('sets kql on hosts page', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); - cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"'); + cy.get(KQL_INPUT).should('have.text', 'source.ip: "10.142.0.9"'); }); it('sets the url state when kql is set', () => { @@ -188,7 +187,7 @@ describe.skip('url state', () => { 'href', `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); - cy.get(HOSTS_NAMES).first().invoke('text').should('eq', 'siem-kibana'); + cy.get(HOSTS_NAMES).first().should('have.text', 'siem-kibana'); openFirstHostDetails(); clearSearchBar(); @@ -218,7 +217,7 @@ describe.skip('url state', () => { it('Do not clears kql when navigating to a new page', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); navigateFromHeaderTo(NETWORK); - cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"'); + cy.get(KQL_INPUT).should('have.text', 'source.ip: "10.142.0.9"'); }); it('sets and reads the url state for timeline by id', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts index ae0c4f35177a9..341ca31715356 100644 --- a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts @@ -26,14 +26,9 @@ import { exportValueList, } from '../tasks/lists'; import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW, VALUE_LISTS_MODAL_ACTIVATOR } from '../screens/lists'; -import { cleanKibana } from '../tasks/common'; describe('value lists', () => { describe('management modal', () => { - before(() => { - cleanKibana(); - }); - beforeEach(() => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index c0299f5ab0c1c..fef94da062e01 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -51,9 +51,12 @@ export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; export const LOCKED_ICON = '[data-test-subj="timeline-date-picker-lock-button"]'; +export const UNLOCKED_ICON = '[data-test-subj="timeline-date-picker-unlock-button"]'; + export const NOTES = '[data-test-subj="note-card-body"]'; -export const NOTE_BY_NOTE_ID = (noteId: string) => `[data-test-subj="note-preview-${noteId}"]`; +export const NOTE_BY_NOTE_ID = (noteId: string) => + `[data-test-subj="note-preview-${noteId}"] .euiMarkdownFormat`; export const NOTE_CONTENT = (noteId: string) => `${NOTE_BY_NOTE_ID(noteId)} p`; @@ -86,6 +89,18 @@ export const SAVE_FILTER_BTN = '[data-test-subj="saveFilter"]'; export const SEARCH_OR_FILTER_CONTAINER = '[data-test-subj="timeline-search-or-filter-search-container"]'; +export const QUERY_TAB_EVENTS_TABLE = '[data-test-subj="query-events-table"]'; + +export const QUERY_TAB_EVENTS_BODY = '[data-test-subj="query-tab-flyout-body"]'; + +export const QUERY_TAB_EVENTS_FOOTER = '[data-test-subj="query-tab-flyout-footer"]'; + +export const PINNED_TAB_EVENTS_TABLE = '[data-test-subj="pinned-events-table"]'; + +export const PINNED_TAB_EVENTS_BODY = '[data-test-subj="pinned-tab-flyout-body"]'; + +export const PINNED_TAB_EVENTS_FOOTER = '[data-test-subj="pinned-tab-flyout-footer"]'; + export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; export const STAR_ICON = '[data-test-subj="timeline-favorite-empty-star"]'; @@ -108,10 +123,8 @@ export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContain export const TIMELINE_FIELDS_BUTTON = '[data-test-subj="timeline"] [data-test-subj="show-field-browser"]'; -export const TIMELINE_FILTER = (filter: TimelineFilter) => { - // The space at the end of the line is required. We want to keep it until it is updated. - return `[data-test-subj="filter filter-enabled filter-key-${filter.field} filter-value-${filter.value} filter-unpinned "]`; -}; +export const TIMELINE_FILTER = (filter: TimelineFilter) => + `[data-test-subj="filter filter-enabled filter-key-${filter.field} filter-value-${filter.value} filter-unpinned"]`; export const TIMELINE_FILTER_FIELD = '[data-test-subj="filterFieldSuggestionList"]'; @@ -124,9 +137,9 @@ export const TIMELINE_FILTER_VALUE = export const TIMELINE_FLYOUT = '[data-test-subj="eui-flyout"]'; -export const TIMELINE_FLYOUT_HEADER = '[data-test-subj="eui-flyout-header"]'; +export const TIMELINE_FLYOUT_HEADER = '[data-test-subj="query-tab-flyout-header"]'; -export const TIMELINE_FLYOUT_BODY = '[data-test-subj="eui-flyout-body"]'; +export const TIMELINE_FLYOUT_BODY = '[data-test-subj="query-tab-flyout-body"]'; export const TIMELINE_INSPECT_BUTTON = `${TIMELINE_FLYOUT} [data-test-subj="inspect-icon-button"]`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts index 39e57f39a145d..94b26fa2e56ea 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts @@ -110,13 +110,18 @@ export const waitForAlerts = () => { }; export const waitForAlertsIndexToBeCreated = () => { - cy.request({ url: '/api/detection_engine/index', retryOnStatusCodeFailure: true }).then( - (response) => { - if (response.status !== 200) { - cy.wait(7500); - } + cy.request({ + url: '/api/detection_engine/index', + failOnStatusCode: false, + }).then((response) => { + if (response.status !== 200) { + cy.request({ + method: 'POST', + url: `/api/detection_engine/index`, + headers: { 'kbn-xsrf': 'create-signals-index' }, + }); } - ); + }); }; export const waitForAlertsPanelToBeLoaded = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 29cdf4ec2be5d..26cc7c87c3055 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -25,6 +25,7 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') => enabled: false, }, headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, }); export const createCustomRuleActivated = (rule: CustomRule, ruleId = 'rule_testing') => @@ -47,6 +48,7 @@ export const createCustomRuleActivated = (rule: CustomRule, ruleId = 'rule_testi tags: ['rule1'], }, headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, }); export const deleteCustomRule = (ruleId = 'rule_testing') => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/timelines.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/timelines.ts index 32c2af1a1866b..8cac4b90fef18 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/timelines.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/timelines.ts @@ -121,5 +121,5 @@ export const getTimelineById = (timelineId: string) => query: 'query GetOneTimeline($id: ID!, $timelineType: TimelineType) {\n getOneTimeline(id: $id, timelineType: $timelineType) {\n savedObjectId\n columns {\n aggregatable\n category\n columnHeaderType\n description\n example\n indexes\n id\n name\n searchable\n type\n __typename\n }\n dataProviders {\n id\n name\n enabled\n excluded\n kqlQuery\n type\n queryMatch {\n field\n displayField\n value\n displayValue\n operator\n __typename\n }\n and {\n id\n name\n enabled\n excluded\n kqlQuery\n type\n queryMatch {\n field\n displayField\n value\n displayValue\n operator\n __typename\n }\n __typename\n }\n __typename\n }\n dateRange {\n start\n end\n __typename\n }\n description\n eventType\n eventIdToNoteIds {\n eventId\n note\n timelineId\n noteId\n created\n createdBy\n timelineVersion\n updated\n updatedBy\n version\n __typename\n }\n excludedRowRendererIds\n favorite {\n fullName\n userName\n favoriteDate\n __typename\n }\n filters {\n meta {\n alias\n controlledBy\n disabled\n field\n formattedValue\n index\n key\n negate\n params\n type\n value\n __typename\n }\n query\n exists\n match_all\n missing\n range\n script\n __typename\n }\n kqlMode\n kqlQuery {\n filterQuery {\n kuery {\n kind\n expression\n __typename\n }\n serializedQuery\n __typename\n }\n __typename\n }\n indexNames\n notes {\n eventId\n note\n timelineId\n timelineVersion\n noteId\n created\n createdBy\n updated\n updatedBy\n version\n __typename\n }\n noteIds\n pinnedEventIds\n pinnedEventsSaveObject {\n pinnedEventId\n eventId\n timelineId\n created\n createdBy\n updated\n updatedBy\n version\n __typename\n }\n status\n title\n timelineType\n templateTimelineId\n templateTimelineVersion\n savedQueryId\n sort\n created\n createdBy\n updated\n updatedBy\n version\n __typename\n }\n}\n', }, - headers: { 'kbn-xsrf': '' }, + headers: { 'kbn-xsrf': 'timeline-by-id' }, }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index c14f50ca35c6b..cd8761ec3ddb2 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { removeSignalsIndex } from './api_calls/rules'; -import { esArchiverLoadEmptyKibana } from './es_archiver'; +import { esArchiverResetKibana } from './es_archiver'; const primaryButton = 0; @@ -58,28 +57,76 @@ export const drop = (dropTarget: JQuery) => { .wait(300); }; -export const reload = (afterReload: () => void) => { +export const reload = () => { cy.reload(); cy.contains('a', 'Security'); - afterReload(); }; export const cleanKibana = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana\*`; + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'alert', + }, + }, + { + match: { + 'alert.alertTypeId': 'siem.signals', + }, + }, + { + match: { + 'alert.consumer': 'siem', + }, + }, + ], + }, + }, + }); - // Delete kibana indexes and wait until they are deleted - cy.request('DELETE', kibanaIndexUrl); - cy.waitUntil(() => { - cy.wait(500); - return cy.request(kibanaIndexUrl).then((response) => JSON.stringify(response.body) === '{}'); + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'cases', + }, + }, + ], + }, + }, }); - // Load kibana indexes and wait until they are created - esArchiverLoadEmptyKibana(); - cy.waitUntil(() => { - cy.wait(500); - return cy.request(kibanaIndexUrl).then((response) => JSON.stringify(response.body) !== '{}'); + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'siem-ui-timeline', + }, + }, + ], + }, + }, }); - removeSignalsIndex(); + cy.request( + 'POST', + `${Cypress.env( + 'ELASTICSEARCH_URL' + )}/.lists-*,.items-*,.siem-signals-*/_delete_by_query?conflicts=proceed&scroll_size=10000`, + { + query: { + match_all: {}, + }, + } + ); + + esArchiverResetKibana(); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts b/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts index 2e1d3379dc202..0e75bc0df2c8c 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts @@ -62,15 +62,11 @@ export const setTimelineStartDate = (date: string) => { }; export const updateDates = () => { - cy.get(DATE_PICKER_APPLY_BUTTON) - .click({ force: true }) - .invoke('text') - .should('not.equal', 'Updating'); + cy.get(DATE_PICKER_APPLY_BUTTON).click({ force: true }).should('not.have.text', 'Updating'); }; export const updateTimelineDates = () => { cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE) .click({ force: true }) - .invoke('text') - .should('not.equal', 'Updating'); + .should('not.have.text', 'Updating'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts b/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts index c0436603a256a..5ebaaf419ed34 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts @@ -4,14 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const esArchiverLoadEmptyKibana = () => { - cy.exec( - `node ../../../scripts/es_archiver load empty_kibana --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( - 'ELASTICSEARCH_URL' - )} --kibana-url ${Cypress.config().baseUrl}` - ); -}; - export const esArchiverLoad = (folder: string) => { cy.exec( `node ../../../scripts/es_archiver load ${folder} --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( @@ -28,18 +20,11 @@ export const esArchiverUnload = (folder: string) => { ); }; -export const esArchiverUnloadEmptyKibana = () => { - cy.exec( - `node ../../../scripts/es_archiver unload empty_kibana --dir ../../test/security_solution_cypress/es_archives --config ../../../test/functional/config.js --es-url ${Cypress.env( - 'ELASTICSEARCH_URL' - )} --kibana-url ${Cypress.config().baseUrl}` - ); -}; - export const esArchiverResetKibana = () => { cy.exec( `node ../../../scripts/es_archiver empty-kibana-index --config ../../../test/functional/config.js --es-url ${Cypress.env( 'ELASTICSEARCH_URL' - )} --kibana-url ${Cypress.config().baseUrl}` + )} --kibana-url ${Cypress.config().baseUrl}`, + { failOnNonZeroExit: false } ); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts index cd64fe4ff1726..7db3f76bac1d1 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/authentications.ts @@ -9,5 +9,5 @@ import { REFRESH_BUTTON } from '../../screens/security_header'; export const waitForAuthenticationsToBeLoaded = () => { cy.get(AUTHENTICATIONS_TABLE).should('exist'); - cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); + cy.get(REFRESH_BUTTON).should('not.have.text', 'Updating'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts index 598def9ed41d0..18f31119ec662 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/uncommon_processes.ts @@ -9,5 +9,5 @@ import { REFRESH_BUTTON } from '../../screens/security_header'; export const waitForUncommonProcessesToBeLoaded = () => { cy.get(UNCOMMON_PROCESSES_TABLE).should('exist'); - cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); + cy.get(REFRESH_BUTTON).should('not.have.text', 'Updating'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 3196181f2a776..47c1fd237432c 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -18,7 +18,7 @@ import { CLOSE_TIMELINE_BTN, COMBO_BOX, CREATE_NEW_TIMELINE, - HEADER, + DRAGGABLE_HEADER, ID_FIELD, ID_HEADER_FIELD, ID_TOGGLE_FIELD, @@ -118,7 +118,6 @@ export const closeTimeline = () => { export const createNewTimeline = () => { cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click({ force: true }); - cy.wait(1000); cy.get(CREATE_NEW_TIMELINE).should('be.visible'); cy.get(CREATE_NEW_TIMELINE).click(); }; @@ -190,8 +189,11 @@ export const dragAndDropIdToggleFieldToTimeline = () => { }; export const removeColumn = (column: number) => { - cy.get(HEADER).eq(column).click(); - cy.get(REMOVE_COLUMN).eq(column).click({ force: true }); + cy.get(DRAGGABLE_HEADER) + .eq(column) + .within(() => { + cy.get(REMOVE_COLUMN).click({ force: true }); + }); }; export const resetFields = () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index d35a5f487ed8e..d6ea611660eda 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -86,7 +86,7 @@ export const NotePreviews = React.memo( return { 'data-test-subj': `note-preview-${note.savedObjectId}`, username: defaultToEmptyTag(note.updatedBy), - event: 'added a comment', + event: i18n.ADDED_A_NOTE, timestamp: note.updated ? ( ) : ( @@ -95,7 +95,7 @@ export const NotePreviews = React.memo( children: (
-

{i18n.USER_ADDED_A_NOTE(note.updatedBy ?? i18n.AN_UNKNOWN_USER)}

+

{`${note.updatedBy ?? i18n.AN_UNKNOWN_USER} ${i18n.ADDED_A_NOTE}`}

{note.note ?? ''}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts index d38dee8a41504..2525173970687 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts @@ -13,14 +13,12 @@ export const TOGGLE_EXPAND_EVENT_DETAILS = i18n.translate( } ); -export const USER_ADDED_A_NOTE = (user: string) => - i18n.translate('xpack.securitySolution.timeline.userAddedANoteScreenReaderOnly', { - values: { user }, - defaultMessage: '{user} added a note', - }); +export const ADDED_A_NOTE = i18n.translate('xpack.securitySolution.timeline.addedANoteLabel', { + defaultMessage: 'added a note', +}); export const AN_UNKNOWN_USER = i18n.translate( - 'xpack.securitySolution.timeline.anUnknownUserScreenReaderOnly', + 'xpack.securitySolution.timeline.anUnknownUserLabel', { defaultMessage: 'an unknown user', } 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 a03f4c07645ad..8f306ef19e036 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 @@ -179,7 +179,7 @@ export const BodyComponent = React.memo( { ); - expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(true); + expect( + wrapper.find(`[data-test-subj="${TimelineTabs.pinned}-events-table"]`).exists() + ).toEqual(true); }); it('it shows the timeline footer', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index 1054b5405d9d9..e204578db610c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -173,10 +173,13 @@ export const PinnedTabContentComponent: React.FC = ({ return ( <> - + - + = ({ />