diff --git a/.buildkite/scripts/bootstrap.sh b/.buildkite/scripts/bootstrap.sh index df38c105d2fd3..272cd0a086170 100755 --- a/.buildkite/scripts/bootstrap.sh +++ b/.buildkite/scripts/bootstrap.sh @@ -6,7 +6,17 @@ source .buildkite/scripts/common/util.sh source .buildkite/scripts/common/setup_bazel.sh echo "--- yarn install and bootstrap" -retry 2 15 yarn kbn bootstrap +if ! yarn kbn bootstrap; then + echo "bootstrap failed, trying again in 15 seconds" + sleep 15 + + # Most bootstrap failures will result in a problem inside node_modules that does not get fixed on the next bootstrap + # So, we should just delete node_modules in between attempts + rm -rf node_modules + + echo "--- yarn install and bootstrap, attempt 2" + yarn kbn bootstrap +fi ### ### upload ts-refs-cache artifacts as quickly as possible so they are available for download diff --git a/docs/api/dashboard-api.asciidoc b/docs/api/dashboard-api.asciidoc index 94511c3154fe0..e6f54dd9156ec 100644 --- a/docs/api/dashboard-api.asciidoc +++ b/docs/api/dashboard-api.asciidoc @@ -1,7 +1,7 @@ [[dashboard-api]] == Import and export dashboard APIs -deprecated::[7.15.0,These experimental APIs have been deprecated in favor of <> and <>.] +deprecated::[7.15.0,Both of these APIs have been deprecated in favor of <> and <>.] Import and export dashboards with the corresponding saved objects, such as visualizations, saved searches, and index patterns. diff --git a/docs/api/dashboard/export-dashboard.asciidoc b/docs/api/dashboard/export-dashboard.asciidoc index 098ec976569bd..3a20eff0a54d2 100644 --- a/docs/api/dashboard/export-dashboard.asciidoc +++ b/docs/api/dashboard/export-dashboard.asciidoc @@ -6,7 +6,7 @@ deprecated::[7.15.0,Use <> instead.] -experimental[] Export dashboards and corresponding saved objects. +Export dashboards and corresponding saved objects. [[dashboard-api-export-request]] ==== Request diff --git a/docs/api/dashboard/import-dashboard.asciidoc b/docs/api/dashboard/import-dashboard.asciidoc index 41eb47500c8d7..e4817d6cb7ee9 100644 --- a/docs/api/dashboard/import-dashboard.asciidoc +++ b/docs/api/dashboard/import-dashboard.asciidoc @@ -6,7 +6,7 @@ deprecated::[7.15.0,Use <> instead.] -experimental[] Import dashboards and corresponding saved objects. +Import dashboards and corresponding saved objects. [[dashboard-api-import-request]] ==== Request diff --git a/packages/kbn-securitysolution-autocomplete/src/field/index.tsx b/packages/kbn-securitysolution-autocomplete/src/field/index.tsx index 69408e919bb1e..a89e0a096b673 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field/index.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { getGenericComboBoxProps, @@ -20,14 +20,14 @@ const AS_PLAIN_TEXT = { asPlainText: true }; interface OperatorProps { fieldInputWidth?: number; fieldTypeFilter?: string[]; - indexPattern: IndexPatternBase | undefined; + indexPattern: DataViewBase | undefined; isClearable: boolean; isDisabled: boolean; isLoading: boolean; isRequired?: boolean; - onChange: (a: IndexPatternFieldBase[]) => void; + onChange: (a: DataViewFieldBase[]) => void; placeholder: string; - selectedField: IndexPatternFieldBase | undefined; + selectedField: DataViewFieldBase | undefined; } export const FieldComponent: React.FC = ({ @@ -56,7 +56,7 @@ export const FieldComponent: React.FC = ({ const handleValuesChange = useCallback( (newOptions: EuiComboBoxOptionOption[]): void => { - const newValues: IndexPatternFieldBase[] = newOptions.map( + const newValues: DataViewFieldBase[] = newOptions.map( ({ label }) => availableFields[labels.indexOf(label)] ); onChange(newValues); @@ -94,13 +94,13 @@ export const FieldComponent: React.FC = ({ FieldComponent.displayName = 'Field'; interface ComboBoxFields { - availableFields: IndexPatternFieldBase[]; - selectedFields: IndexPatternFieldBase[]; + availableFields: DataViewFieldBase[]; + selectedFields: DataViewFieldBase[]; } const getComboBoxFields = ( - indexPattern: IndexPatternBase | undefined, - selectedField: IndexPatternFieldBase | undefined, + indexPattern: DataViewBase | undefined, + selectedField: DataViewFieldBase | undefined, fieldTypeFilter: string[] ): ComboBoxFields => { const existingFields = getExistingFields(indexPattern); @@ -113,29 +113,27 @@ const getComboBoxFields = ( const getComboBoxProps = (fields: ComboBoxFields): GetGenericComboBoxPropsReturn => { const { availableFields, selectedFields } = fields; - return getGenericComboBoxProps({ + return getGenericComboBoxProps({ getLabel: (field) => field.name, options: availableFields, selectedOptions: selectedFields, }); }; -const getExistingFields = (indexPattern: IndexPatternBase | undefined): IndexPatternFieldBase[] => { +const getExistingFields = (indexPattern: DataViewBase | undefined): DataViewFieldBase[] => { return indexPattern != null ? indexPattern.fields : []; }; -const getSelectedFields = ( - selectedField: IndexPatternFieldBase | undefined -): IndexPatternFieldBase[] => { +const getSelectedFields = (selectedField: DataViewFieldBase | undefined): DataViewFieldBase[] => { return selectedField ? [selectedField] : []; }; const getAvailableFields = ( - existingFields: IndexPatternFieldBase[], - selectedFields: IndexPatternFieldBase[], + existingFields: DataViewFieldBase[], + selectedFields: DataViewFieldBase[], fieldTypeFilter: string[] -): IndexPatternFieldBase[] => { - const fieldsByName = new Map(); +): DataViewFieldBase[] => { + const fieldsByName = new Map(); existingFields.forEach((f) => fieldsByName.set(f.name, f)); selectedFields.forEach((f) => fieldsByName.set(f.name, f)); diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts index a65d4d551cf7c..1a8b705480258 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts @@ -5,7 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { IndexPatternsFetcher } from '.'; import { ElasticsearchClient } from 'kibana/server'; import * as indexNotFoundException from './index_not_found_exception.json'; @@ -15,36 +14,36 @@ describe('Index Pattern Fetcher - server', () => { let esClient: ElasticsearchClient; const emptyResponse = { body: { - count: 0, + indices: [], }, }; const response = { body: { - count: 1115, + indices: ['b'], + fields: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }], }, }; const patternList = ['a', 'b', 'c']; beforeEach(() => { + jest.clearAllMocks(); esClient = { - count: jest.fn().mockResolvedValueOnce(emptyResponse).mockResolvedValue(response), + fieldCaps: jest.fn().mockResolvedValueOnce(emptyResponse).mockResolvedValue(response), } as unknown as ElasticsearchClient; indexPatterns = new IndexPatternsFetcher(esClient); }); - it('Removes pattern without matching indices', async () => { const result = await indexPatterns.validatePatternListActive(patternList); expect(result).toEqual(['b', 'c']); }); - it('Returns all patterns when all match indices', async () => { esClient = { - count: jest.fn().mockResolvedValue(response), + fieldCaps: jest.fn().mockResolvedValue(response), } as unknown as ElasticsearchClient; indexPatterns = new IndexPatternsFetcher(esClient); const result = await indexPatterns.validatePatternListActive(patternList); expect(result).toEqual(patternList); }); - it('Removes pattern when "index_not_found_exception" error is thrown', async () => { + it('Removes pattern when error is thrown', async () => { class ServerError extends Error { public body?: Record; constructor( @@ -56,9 +55,8 @@ describe('Index Pattern Fetcher - server', () => { this.body = errBody; } } - esClient = { - count: jest + fieldCaps: jest .fn() .mockResolvedValueOnce(response) .mockRejectedValue( @@ -69,4 +67,22 @@ describe('Index Pattern Fetcher - server', () => { const result = await indexPatterns.validatePatternListActive(patternList); expect(result).toEqual([patternList[0]]); }); + it('When allowNoIndices is false, run validatePatternListActive', async () => { + const fieldCapsMock = jest.fn(); + esClient = { + fieldCaps: fieldCapsMock.mockResolvedValue(response), + } as unknown as ElasticsearchClient; + indexPatterns = new IndexPatternsFetcher(esClient); + await indexPatterns.getFieldsForWildcard({ pattern: patternList }); + expect(fieldCapsMock.mock.calls).toHaveLength(4); + }); + it('When allowNoIndices is true, do not run validatePatternListActive', async () => { + const fieldCapsMock = jest.fn(); + esClient = { + fieldCaps: fieldCapsMock.mockResolvedValue(response), + } as unknown as ElasticsearchClient; + indexPatterns = new IndexPatternsFetcher(esClient, true); + await indexPatterns.getFieldsForWildcard({ pattern: patternList }); + expect(fieldCapsMock.mock.calls).toHaveLength(1); + }); }); diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts index 7dae85c920ebf..c054d547e956f 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts @@ -36,12 +36,10 @@ interface FieldSubType { export class IndexPatternsFetcher { private elasticsearchClient: ElasticsearchClient; private allowNoIndices: boolean; - constructor(elasticsearchClient: ElasticsearchClient, allowNoIndices: boolean = false) { this.elasticsearchClient = elasticsearchClient; this.allowNoIndices = allowNoIndices; } - /** * Get a list of field objects for an index pattern that may contain wildcards * @@ -60,23 +58,22 @@ export class IndexPatternsFetcher { }): Promise { const { pattern, metaFields, fieldCapsOptions, type, rollupIndex } = options; const patternList = Array.isArray(pattern) ? pattern : pattern.split(','); + const allowNoIndices = fieldCapsOptions + ? fieldCapsOptions.allow_no_indices + : this.allowNoIndices; let patternListActive: string[] = patternList; // if only one pattern, don't bother with validation. We let getFieldCapabilities fail if the single pattern is bad regardless - if (patternList.length > 1) { + if (patternList.length > 1 && !allowNoIndices) { patternListActive = await this.validatePatternListActive(patternList); } const fieldCapsResponse = await getFieldCapabilities( this.elasticsearchClient, - // if none of the patterns are active, pass the original list to get an error - patternListActive.length > 0 ? patternListActive : patternList, + patternListActive, metaFields, { - allow_no_indices: fieldCapsOptions - ? fieldCapsOptions.allow_no_indices - : this.allowNoIndices, + allow_no_indices: allowNoIndices, } ); - if (type === 'rollup' && rollupIndex) { const rollupFields: FieldDescriptor[] = []; const rollupIndexCapabilities = getCapabilitiesForRollupIndices( @@ -87,13 +84,11 @@ export class IndexPatternsFetcher { ).body )[rollupIndex].aggs; const fieldCapsResponseObj = keyBy(fieldCapsResponse, 'name'); - // Keep meta fields metaFields!.forEach( (field: string) => fieldCapsResponseObj[field] && rollupFields.push(fieldCapsResponseObj[field]) ); - return mergeCapabilitiesWithFields( rollupIndexCapabilities, fieldCapsResponseObj, @@ -137,23 +132,20 @@ export class IndexPatternsFetcher { async validatePatternListActive(patternList: string[]) { const result = await Promise.all( patternList - .map((pattern) => - this.elasticsearchClient.count({ - index: pattern, - }) - ) - .map((p) => - p.catch((e) => { - if (e.body.error.type === 'index_not_found_exception') { - return { body: { count: 0 } }; - } - throw e; - }) - ) + .map(async (index) => { + const searchResponse = await this.elasticsearchClient.fieldCaps({ + index, + fields: '_id', + ignore_unavailable: true, + allow_no_indices: false, + }); + return searchResponse.body.indices.length > 0; + }) + .map((p) => p.catch(() => false)) ); return result.reduce( - (acc: string[], { body: { count } }, patternListIndex) => - count > 0 ? [...acc, patternList[patternListIndex]] : acc, + (acc: string[], isValid, patternListIndex) => + isValid ? [...acc, patternList[patternListIndex]] : acc, [] ); } diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js index 66b4b164a794c..cc985407698bf 100644 --- a/x-pack/plugins/apm/jest.config.js +++ b/x-pack/plugins/apm/jest.config.js @@ -19,5 +19,6 @@ module.exports = { coverageReporters: ['text', 'html'], collectCoverageFrom: [ '/x-pack/plugins/apm/{common,public,server}/**/*.{js,ts,tsx}', + '!/**/*.stories.*', ], }; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx new file mode 100644 index 0000000000000..0a4adc07e1a98 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta, Story } from '@storybook/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { CoreStart } from '../../../../../../../src/core/public'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; +import { TimeRangeComparisonEnum } from '../../../../common/runtime_types/comparison_type_rt'; +import { AnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { ServiceInventory } from './'; + +const stories: Meta<{}> = { + title: 'app/ServiceInventory', + component: ServiceInventory, + decorators: [ + (StoryComponent) => { + const coreMock = { + http: { + get: (endpoint: string) => { + switch (endpoint) { + case '/internal/apm/services': + return { items: [] }; + default: + return {}; + } + return {}; + }, + }, + notifications: { toasts: { add: () => {}, addWarning: () => {} } }, + uiSettings: { get: () => [] }, + } as unknown as CoreStart; + + const KibanaReactContext = createKibanaReactContext(coreMock); + + const anomlyDetectionJobsContextValue = { + anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, + anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, + anomalyDetectionJobsRefetch: () => {}, + }; + + return ( + + + + + + + + + + + + ); + }, + ], +}; +export default stories; + +export const Example: Story<{}> = () => { + return ; +}; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index 4a020f9b0db4e..36b1053248d25 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -5,249 +5,17 @@ * 2.0. */ -import { render, waitFor } from '@testing-library/react'; -import { CoreStart } from 'kibana/public'; -import { merge } from 'lodash'; -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ServiceHealthStatus } from '../../../../common/service_health_status'; -import { TimeRangeComparisonEnum } from '../../../../common/runtime_types/comparison_type_rt'; -import { ServiceInventory } from '.'; -import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; -import { - mockApmPluginContextValue, - MockApmPluginContextWrapper, -} from '../../../context/apm_plugin/mock_apm_plugin_context'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { clearCache } from '../../../services/rest/callApi'; -import * as useDynamicDataViewHooks from '../../../hooks/use_dynamic_data_view'; -import { SessionStorageMock } from '../../../services/__mocks__/SessionStorageMock'; -import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; -import * as hook from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; +import { composeStories } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import * as stories from './service_inventory.stories'; -const KibanaReactContext = createKibanaReactContext({ - usageCollection: { reportUiCounter: () => {} }, -} as Partial); - -const addWarning = jest.fn(); -const httpGet = jest.fn(); - -function wrapper({ children }: { children?: ReactNode }) { - const mockPluginContext = merge({}, mockApmPluginContextValue, { - core: { - http: { - get: httpGet, - }, - notifications: { - toasts: { - addWarning, - }, - }, - }, - }) as unknown as ApmPluginContextValue; - - return ( - - - - - - {children} - - - - - - ); -} +const { Example } = composeStories(stories); describe('ServiceInventory', () => { - beforeEach(() => { - // @ts-expect-error - global.sessionStorage = new SessionStorageMock(); - clearCache(); - - jest.spyOn(hook, 'useAnomalyDetectionJobsContext').mockReturnValue({ - anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, - anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, - anomalyDetectionJobsRefetch: () => {}, - }); - - jest - .spyOn(useDynamicDataViewHooks, 'useDynamicDataViewFetcher') - .mockReturnValue({ - dataView: undefined, - status: FETCH_STATUS.SUCCESS, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should render services, when list is not empty', async () => { - // mock rest requests - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [ - { - serviceName: 'My Python Service', - agentName: 'python', - transactionsPerMinute: 100, - errorsPerMinute: 200, - avgResponseTime: 300, - environments: ['test', 'dev'], - healthStatus: ServiceHealthStatus.warning, - }, - { - serviceName: 'My Go Service', - agentName: 'go', - transactionsPerMinute: 400, - errorsPerMinute: 500, - avgResponseTime: 600, - environments: [], - severity: ServiceHealthStatus.healthy, - }, - ], - }); - - const { container, findByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - await findByText('My Python Service'); - - expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2); - }); - - it('should render empty message, when list is empty and historical data is found', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [], - }); - - const { findByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - const noServicesText = await findByText('No services found'); - - expect(noServicesText).not.toBeEmptyDOMElement(); - }); - - describe('when legacy data is found', () => { - it('renders an upgrade migration notification', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: true, - hasHistoricalData: true, - items: [], - }); - - render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - expect(addWarning).toHaveBeenLastCalledWith( - expect.objectContaining({ - title: 'Legacy data was detected within the selected time range', - }) - ); - }); - }); - - describe('when legacy data is not found', () => { - it('does not render an upgrade migration notification', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [], - }); - - render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - expect(addWarning).not.toHaveBeenCalled(); - }); - }); - - describe('when ML data is not found', () => { - it('does not render the health column', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [ - { - serviceName: 'My Python Service', - agentName: 'python', - transactionsPerMinute: 100, - errorsPerMinute: 200, - avgResponseTime: 300, - environments: ['test', 'dev'], - }, - ], - }); - - const { queryByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - expect(queryByText('Health')).toBeNull(); - }); - }); - - describe('when ML data is found', () => { - it('renders the health column', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [ - { - serviceName: 'My Python Service', - agentName: 'python', - transactionsPerMinute: 100, - errorsPerMinute: 200, - avgResponseTime: 300, - environments: ['test', 'dev'], - healthStatus: ServiceHealthStatus.warning, - }, - ], - }); - - const { queryAllByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); + it('renders', async () => { + render(); - expect(queryAllByText('Health').length).toBeGreaterThan(1); - }); + expect(await screen.findByRole('table')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx new file mode 100644 index 0000000000000..b632d3a33dea8 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta, Story } from '@storybook/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import type { CoreStart } from '../../../../../../../src/core/public'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; +import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { + APMServiceContext, + APMServiceContextValue, +} from '../../../context/apm_service/apm_service_context'; +import { ServiceOverview } from './'; + +const stories: Meta<{}> = { + title: 'app/ServiceOverview', + component: ServiceOverview, + decorators: [ + (StoryComponent) => { + const serviceName = 'testServiceName'; + const mockCore = { + http: { + basePath: { prepend: () => {} }, + get: (endpoint: string) => { + switch (endpoint) { + case `/api/apm/services/${serviceName}/annotation/search`: + return { annotations: [] }; + case '/internal/apm/fallback_to_transactions': + return { fallbackToTransactions: false }; + case `/internal/apm/services/${serviceName}/dependencies`: + return { serviceDependencies: [] }; + default: + return {}; + } + }, + }, + notifications: { toasts: { add: () => {} } }, + uiSettings: { get: () => 'Browser' }, + } as unknown as CoreStart; + const serviceContextValue = { + alerts: [], + serviceName, + } as unknown as APMServiceContextValue; + const KibanaReactContext = createKibanaReactContext(mockCore); + + return ( + + + + + + + + + + ); + }, + ], +}; +export default stories; + +export const Example: Story<{}> = () => { + return ; +}; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index 7e9b4325591d9..fb60604aa53b2 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -5,178 +5,19 @@ * 2.0. */ -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { CoreStart } from 'src/core/public'; -import { isEqual } from 'lodash'; -import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; -import { - mockApmPluginContextValue, - MockApmPluginContextWrapper, -} from '../../../context/apm_plugin/mock_apm_plugin_context'; -import * as useDynamicDataViewHooks from '../../../hooks/use_dynamic_data_view'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context'; -import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_breakdown_chart/use_transaction_breakdown'; -import { renderWithTheme } from '../../../utils/testHelpers'; -import { ServiceOverview } from './'; -import { waitFor } from '@testing-library/dom'; -import * as useApmServiceContextHooks from '../../../context/apm_service/use_apm_service_context'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { - getCallApmApiSpy, - getCreateCallApmApiSpy, -} from '../../../services/rest/callApmApiSpy'; -import { fromQuery } from '../../shared/Links/url_helpers'; -import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; -import { uiSettingsServiceMock } from '../../../../../../../src/core/public/mocks'; +import { composeStories } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import * as stories from './service_overview.stories'; -const uiSettings = uiSettingsServiceMock.create().setup({} as any); - -const KibanaReactContext = createKibanaReactContext({ - notifications: { toasts: { add: () => {} } }, - uiSettings, - usageCollection: { reportUiCounter: () => {} }, -} as unknown as Partial); - -const mockParams = { - rangeFrom: 'now-15m', - rangeTo: 'now', - latencyAggregationType: LatencyAggregationType.avg, -}; - -const location = { - pathname: '/services/test%20service%20name/overview', - search: fromQuery(mockParams), -}; - -function Wrapper({ children }: { children?: ReactNode }) { - const value = { - ...mockApmPluginContextValue, - core: { - ...mockApmPluginContextValue.core, - http: { - basePath: { prepend: () => {} }, - get: () => {}, - }, - }, - } as unknown as ApmPluginContextValue; - - return ( - - - - - {children} - - - - - ); -} +const { Example } = composeStories(stories); describe('ServiceOverview', () => { it('renders', async () => { - jest - .spyOn(useApmServiceContextHooks, 'useApmServiceContext') - .mockReturnValue({ - serviceName: 'test service name', - agentName: 'java', - transactionType: 'request', - transactionTypes: ['request'], - alerts: [], - }); - jest - .spyOn(useAnnotationsHooks, 'useAnnotationsContext') - .mockReturnValue({ annotations: [] }); - jest - .spyOn(useDynamicDataViewHooks, 'useDynamicDataViewFetcher') - .mockReturnValue({ - dataView: undefined, - status: FETCH_STATUS.SUCCESS, - }); - - /* eslint-disable @typescript-eslint/naming-convention */ - const calls = { - 'GET /internal/apm/services/{serviceName}/error_groups/main_statistics': { - error_groups: [] as any[], - }, - 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics': - { - transactionGroups: [] as any[], - totalTransactionGroups: 0, - isAggregationAccurate: true, - }, - 'GET /internal/apm/services/{serviceName}/dependencies': { - serviceDependencies: [], - }, - 'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics': - [], - 'GET /internal/apm/services/{serviceName}/transactions/charts/latency': { - currentPeriod: { - overallAvgDuration: null, - latencyTimeseries: [], - }, - previousPeriod: { - overallAvgDuration: null, - latencyTimeseries: [], - }, - }, - 'GET /internal/apm/services/{serviceName}/throughput': { - currentPeriod: [], - previousPeriod: [], - }, - 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate': - { - currentPeriod: { - timeseries: [], - average: null, - }, - previousPeriod: { - timeseries: [], - average: null, - }, - }, - 'GET /api/apm/services/{serviceName}/annotation/search': { - annotations: [], - }, - 'GET /internal/apm/fallback_to_transactions': { - fallbackToTransactions: false, - }, - }; - /* eslint-enable @typescript-eslint/naming-convention */ - - const callApmApiSpy = getCallApmApiSpy().mockImplementation( - ({ endpoint }) => { - const response = calls[endpoint as keyof typeof calls]; - - return response - ? Promise.resolve(response) - : Promise.reject(`Response for ${endpoint} is not defined`); - } - ); - - getCreateCallApmApiSpy().mockImplementation(() => callApmApiSpy as any); - jest - .spyOn(useTransactionBreakdownHooks, 'useTransactionBreakdown') - .mockReturnValue({ - data: { timeseries: [] }, - error: undefined, - status: FETCH_STATUS.SUCCESS, - }); - - const { findAllByText } = renderWithTheme(, { - wrapper: Wrapper, - }); - - await waitFor(() => { - const endpoints = callApmApiSpy.mock.calls.map( - (call) => call[0].endpoint - ); - return isEqual(endpoints.sort(), Object.keys(calls).sort()); - }); + render(); - expect((await findAllByText('Latency')).length).toBeGreaterThan(0); + expect( + await screen.findByRole('heading', { name: /Latency/ }) + ).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 9d207eee2fbaa..c99ef519f9e69 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -23,14 +23,20 @@ export type APMServiceAlert = ValuesType< APIReturnType<'GET /internal/apm/services/{serviceName}/alerts'>['alerts'] >; -export const APMServiceContext = createContext<{ +export interface APMServiceContextValue { serviceName: string; agentName?: string; transactionType?: string; transactionTypes: string[]; alerts: APMServiceAlert[]; runtimeName?: string; -}>({ serviceName: '', transactionTypes: [], alerts: [] }); +} + +export const APMServiceContext = createContext({ + serviceName: '', + transactionTypes: [], + alerts: [], +}); export function ApmServiceContextProvider({ children, diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index ace01aa851ce8..13ef1c7725a3d 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -412,6 +412,8 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { }, renderCellValue: getRenderCellValue({ setFlyoutAlert }), rowRenderers: NO_ROW_RENDER, + // TODO: implement Kibana data view runtime fields in observability + runtimeMappings: {}, start: rangeFrom, setRefetch, sort: [ diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 2772c3de51065..584f3ed334d89 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -11,8 +11,16 @@ import type { TransformConfigSchema } from './transforms/types'; import { ENABLE_CASE_CONNECTOR } from '../../cases/common'; import { METADATA_TRANSFORMS_PATTERN } from './endpoint/constants'; +/** + * as const + * + * The const assertion ensures that type widening does not occur + * https://mariusschulz.com/blog/literal-type-widening-in-typescript + * Please follow this convention when adding to this file + */ + export const APP_ID = 'securitySolution' as const; -export const APP_UI_ID = 'securitySolutionUI'; +export const APP_UI_ID = 'securitySolutionUI' as const; export const CASES_FEATURE_ID = 'securitySolutionCases' as const; export const SERVER_APP_ID = 'siem' as const; export const APP_NAME = 'Security' as const; @@ -26,6 +34,8 @@ export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; export const DEFAULT_DARK_MODE = 'theme:darkMode' as const; export const DEFAULT_INDEX_KEY = 'securitySolution:defaultIndex' as const; export const DEFAULT_NUMBER_FORMAT = 'format:number:defaultPattern' as const; +export const DEFAULT_DATA_VIEW_ID = 'security-solution' as const; +export const DEFAULT_TIME_FIELD = '@timestamp' as const; export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults' as const; export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults' as const; export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults' as const; @@ -51,7 +61,6 @@ export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges' as const export const DEFAULT_TRANSFORMS = 'securitySolution:transforms' as const; export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled' as const; export const GLOBAL_HEADER_HEIGHT = 96 as const; // px -export const GLOBAL_HEADER_HEIGHT_WITH_GLOBAL_BANNER = 128 as const; // px export const FILTERS_GLOBAL_HEIGHT = 109 as const; // px export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled' as const; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51' as const; @@ -268,6 +277,7 @@ export const TIMELINE_PREPACKAGED_URL = `${TIMELINE_URL}/_prepackaged` as const; export const NOTE_URL = '/api/note' as const; export const PINNED_EVENT_URL = '/api/pinned_event' as const; +export const SOURCERER_API_URL = '/api/sourcerer' as const; /** * Default signals index key for kibana.dev.yml @@ -355,7 +365,7 @@ export const ELASTIC_NAME = 'estc' as const; export const METADATA_TRANSFORM_STATS_URL = `/api/transform/transforms/${METADATA_TRANSFORMS_PATTERN}/_stats`; -export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_latest_'; +export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_latest_' as const; export const TRANSFORM_STATES = { ABORTING: 'aborting', diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts index 033e979d2814c..42c10614975eb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts @@ -11,7 +11,7 @@ import type { CreateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { buildExceptionFilter } from '@kbn/securitysolution-list-utils'; -import { Filter, EsQueryConfig, IndexPatternBase, buildEsQuery } from '@kbn/es-query'; +import { Filter, EsQueryConfig, DataViewBase, buildEsQuery } from '@kbn/es-query'; import { ESBoolQuery } from '../typed_json'; import { Query, Index, TimestampOverrideOrUndefined } from './schemas/common/schemas'; @@ -24,7 +24,7 @@ export const getQueryFilter = ( lists: Array, excludeExceptions: boolean = true ): ESBoolQuery => { - const indexPattern: IndexPatternBase = { + const indexPattern: DataViewBase = { fields: [], title: index.join(), }; diff --git a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts index be5fd3b5c4dc5..86bc11f7a596d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts @@ -5,79 +5,17 @@ * 2.0. */ -import type { IFieldSubType } from '@kbn/es-query'; - -import type { - IEsSearchRequest, - IEsSearchResponse, - IIndexPattern, -} from '../../../../../../src/plugins/data/common'; -import type { DocValueFields, Maybe } from '../common'; - -interface FieldInfo { - category: string; - description?: string; - example?: string | number; - format?: string; - name: string; - type?: string; -} - -export interface IndexField { - /** Where the field belong */ - category: string; - /** Example of field's value */ - example?: Maybe; - /** whether the field's belong to an alias index */ - indexes: Array>; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Description of the field */ - description?: Maybe; - format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: string[]; - subType?: IFieldSubType; - readFromDocValues: boolean; -} - -export type BeatFields = Record; - -export interface IndexFieldsStrategyRequest extends IEsSearchRequest { - indices: string[]; - onlyCheckIfIndicesExist: boolean; -} - -export interface IndexFieldsStrategyResponse extends IEsSearchResponse { - indexFields: IndexField[]; - indicesExist: string[]; -} - -export interface BrowserField { - aggregatable: boolean; - category: string; - description: string | null; - example: string | number | null; - fields: Readonly>>; - format: string; - indexes: string[]; - name: string; - searchable: boolean; - type: string; - subType?: IFieldSubType; -} - -export type BrowserFields = Readonly>>; - -export const EMPTY_BROWSER_FIELDS = {}; -export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; -export const EMPTY_INDEX_PATTERN: IIndexPattern = { - fields: [], - title: '', -}; +export type { + FieldInfo, + IndexField, + BeatFields, + IndexFieldsStrategyRequest, + IndexFieldsStrategyResponse, + BrowserField, + BrowserFields, +} from '../../../../timelines/common'; +export { + EMPTY_BROWSER_FIELDS, + EMPTY_DOCVALUE_FIELD, + EMPTY_INDEX_FIELDS, +} from '../../../../timelines/common'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts index 39f23a63c8afe..d6735b59c229d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts @@ -10,6 +10,7 @@ export { LastEventIndexKey } from '../../../../../../timelines/common'; export type { LastTimeDetails, TimelineEventsLastEventTimeStrategyResponse, + TimelineKpiStrategyRequest, TimelineKpiStrategyResponse, TimelineEventsLastEventTimeRequestOptions, } from '../../../../../../timelines/common'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index 548560ac5cb8c..2d94a36a937d5 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; import { ESQuery } from '../../typed_json'; import { @@ -41,6 +42,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest { defaultIndex: string[]; docValueFields?: DocValueFields[]; factoryQueryType?: TimelineFactoryQueryTypes; + runtimeMappings: MappingRuntimeFields; } export interface TimelineRequestSortField extends SortField { @@ -171,6 +173,7 @@ export interface SortTimelineInput { export interface TimelineInput { columns?: Maybe; dataProviders?: Maybe; + dataViewId?: Maybe; description?: Maybe; eqlOptions?: Maybe; eventType?: Maybe; diff --git a/x-pack/plugins/security_solution/common/test/index.ts b/x-pack/plugins/security_solution/common/test/index.ts index 6d5df76b306a3..53261d54e84b0 100644 --- a/x-pack/plugins/security_solution/common/test/index.ts +++ b/x-pack/plugins/security_solution/common/test/index.ts @@ -7,12 +7,12 @@ // For the source of these roles please consult the PR these were introduced https://github.com/elastic/kibana/pull/81866#issue-511165754 export enum ROLES { + soc_manager = 'soc_manager', reader = 'reader', t1_analyst = 't1_analyst', t2_analyst = 't2_analyst', hunter = 'hunter', rule_author = 'rule_author', - soc_manager = 'soc_manager', platform_engineer = 'platform_engineer', detections_admin = 'detections_admin', } diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index c0046f7535db8..60fd126e6fd85 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -272,6 +272,7 @@ export type TimelineTypeLiteralWithNull = runtimeTypes.TypeOf; +export type TimelineWithoutExternalRefs = Omit; /* * Timeline IDs @@ -719,6 +720,7 @@ export interface TimelineResult { created?: Maybe; createdBy?: Maybe; dataProviders?: Maybe; + dataViewId?: Maybe; dateRange?: Maybe; description?: Maybe; eqlOptions?: Maybe; diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts index 03cf0c39378e5..75cd44ba2b7d7 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/store.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts @@ -38,19 +38,20 @@ export interface SortColumnTimeline { } export interface TimelinePersistInput { - id: string; + columns: ColumnHeaderOptions[]; dataProviders?: DataProvider[]; + dataViewId: string; dateRange?: { start: string; end: string; }; + defaultColumns?: ColumnHeaderOptions[]; excludedRowRendererIds?: RowRendererId[]; expandedDetail?: TimelineExpandedDetail; filters?: Filter[]; - columns: ColumnHeaderOptions[]; - defaultColumns?: ColumnHeaderOptions[]; - itemsPerPage?: number; + id: string; indexNames: string[]; + itemsPerPage?: number; kqlQuery?: { filterQuery: SerializedFilterQuery | null; }; diff --git a/x-pack/plugins/security_solution/cypress/cypress.json b/x-pack/plugins/security_solution/cypress/cypress.json index 6a9a240af5873..8c27309becf08 100644 --- a/x-pack/plugins/security_solution/cypress/cypress.json +++ b/x-pack/plugins/security_solution/cypress/cypress.json @@ -12,5 +12,10 @@ "video": false, "videosFolder": "../../../target/kibana-security-solution/cypress/videos", "viewportHeight": 900, - "viewportWidth": 1440 + "viewportWidth": 1440, + "env": { + "protocol": "http", + "hostname": "localhost", + "configport": "5601" + } } diff --git a/x-pack/plugins/security_solution/cypress/downloads/timelines_export.ndjson b/x-pack/plugins/security_solution/cypress/downloads/timelines_export.ndjson new file mode 100644 index 0000000000000..8cf76734ad876 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/downloads/timelines_export.ndjson @@ -0,0 +1 @@ +{"savedObjectId":"46cca0e0-2580-11ec-8e56-9dafa0b0343b","version":"WzIyNjIzNCwxXQ==","columns":[{"id":"@timestamp"},{"id":"user.name"},{"id":"event.category"},{"id":"event.action"},{"id":"host.name"}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"host.name: *","kind":"kuery"}}},"dateRange":{"start":"1514809376000","end":"1577881376000"},"description":"This is the best timeline","title":"Security Timeline","created":1633399341550,"createdBy":"elastic","updated":1633399341550,"updatedBy":"elastic","savedQueryId":null,"dataViewId":null,"timelineType":"default","sort":[],"eventNotes":[],"globalNotes":[],"pinnedEventIds":[]} diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts index 23016ecc512b1..0337cd3bd6e17 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts @@ -18,184 +18,28 @@ import { filterStatusOpen, } from '../../tasks/create_new_case'; import { - constructUrlWithUser, - getEnvAuth, + loginAndWaitForHostDetailsPage, loginWithUserAndWaitForPageWithoutDateRange, + logout, } from '../../tasks/login'; +import { + createUsersAndRoles, + deleteUsersAndRoles, + secAll, + secAllUser, + secReadCasesAllUser, + secReadCasesAll, +} from '../../tasks/privileges'; import { CASES_URL } from '../../urls/navigation'; - -interface User { - username: string; - password: string; - description?: string; - roles: string[]; -} - -interface UserInfo { - username: string; - full_name: string; - email: string; -} - -interface FeaturesPrivileges { - [featureId: string]: string[]; -} - -interface ElasticsearchIndices { - names: string[]; - privileges: string[]; -} - -interface ElasticSearchPrivilege { - cluster?: string[]; - indices?: ElasticsearchIndices[]; -} - -interface KibanaPrivilege { - spaces: string[]; - base?: string[]; - feature?: FeaturesPrivileges; -} - -interface Role { - name: string; - privileges: { - elasticsearch?: ElasticSearchPrivilege; - kibana?: KibanaPrivilege[]; - }; -} - -const secAll: Role = { - name: 'sec_all_role', - privileges: { - elasticsearch: { - indices: [ - { - names: ['*'], - privileges: ['all'], - }, - ], - }, - kibana: [ - { - feature: { - siem: ['all'], - securitySolutionCases: ['all'], - actions: ['all'], - actionsSimulators: ['all'], - }, - spaces: ['*'], - }, - ], - }, -}; - -const secAllUser: User = { - username: 'sec_all_user', - password: 'password', - roles: [secAll.name], -}; - -const secReadCasesAll: Role = { - name: 'sec_read_cases_all_role', - privileges: { - elasticsearch: { - indices: [ - { - names: ['*'], - privileges: ['all'], - }, - ], - }, - kibana: [ - { - feature: { - siem: ['read'], - securitySolutionCases: ['all'], - actions: ['all'], - actionsSimulators: ['all'], - }, - spaces: ['*'], - }, - ], - }, -}; - -const secReadCasesAllUser: User = { - username: 'sec_read_cases_all_user', - password: 'password', - roles: [secReadCasesAll.name], -}; - +import { openSourcerer } from '../../tasks/sourcerer'; const usersToCreate = [secAllUser, secReadCasesAllUser]; const rolesToCreate = [secAll, secReadCasesAll]; - -const getUserInfo = (user: User): UserInfo => ({ - username: user.username, - full_name: user.username.replace('_', ' '), - email: `${user.username}@elastic.co`, -}); - -const createUsersAndRoles = (users: User[], roles: Role[]) => { - const envUser = getEnvAuth(); - for (const role of roles) { - cy.log(`Creating role: ${JSON.stringify(role)}`); - cy.request({ - body: role.privileges, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'PUT', - url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), - }) - .its('status') - .should('eql', 204); - } - - for (const user of users) { - const userInfo = getUserInfo(user); - cy.log(`Creating user: ${JSON.stringify(user)}`); - cy.request({ - body: { - username: user.username, - password: user.password, - roles: user.roles, - full_name: userInfo.full_name, - email: userInfo.email, - }, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'POST', - url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), - }) - .its('status') - .should('eql', 200); - } -}; - -const deleteUsersAndRoles = (users: User[], roles: Role[]) => { - const envUser = getEnvAuth(); - for (const user of users) { - cy.log(`Deleting user: ${JSON.stringify(user)}`); - cy.request({ - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'DELETE', - url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), - failOnStatusCode: false, - }) - .its('status') - .should('oneOf', [204, 404]); - } - - for (const role of roles) { - cy.log(`Deleting role: ${JSON.stringify(role)}`); - cy.request({ - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'DELETE', - url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), - failOnStatusCode: false, - }) - .its('status') - .should('oneOf', [204, 404]); - } +// needed to generate index pattern +const visitSecuritySolution = () => { + loginAndWaitForHostDetailsPage(); + openSourcerer(); + logout(); }; const testCase: TestCaseWithoutTimeline = { @@ -205,11 +49,11 @@ const testCase: TestCaseWithoutTimeline = { reporter: 'elastic', owner: 'securitySolution', }; - describe('Cases privileges', () => { before(() => { cleanKibana(); createUsersAndRoles(usersToCreate, rolesToCreate); + visitSecuritySolution(); }); after(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts index 26c366e981d44..bd7acc38c1021 100644 --- a/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { loginAndWaitForPage } from '../../tasks/login'; +import { + loginAndWaitForPage, + loginWithUserAndWaitForPageWithoutDateRange, +} from '../../tasks/login'; import { HOSTS_URL } from '../../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts'; @@ -28,20 +31,34 @@ import { openTimelineUsingToggle } from '../../tasks/security_main'; import { populateTimeline } from '../../tasks/timeline'; import { SERVER_SIDE_EVENT_COUNT } from '../../screens/timeline'; import { cleanKibana } from '../../tasks/common'; +import { createUsersAndRoles, secReadCasesAll, secReadCasesAllUser } from '../../tasks/privileges'; +import { TOASTER } from '../../screens/configure_cases'; +const usersToCreate = [secReadCasesAllUser]; +const rolesToCreate = [secReadCasesAll]; // Skipped at the moment as this has flake due to click handler issues. This has been raised with team members // and the code is being re-worked and then these tests will be unskipped -describe.skip('Sourcerer', () => { - before(() => { +describe('Sourcerer', () => { + beforeEach(() => { cleanKibana(); }); - - beforeEach(() => { - cy.clearLocalStorage(); - loginAndWaitForPage(HOSTS_URL); + describe('permissions', () => { + before(() => { + createUsersAndRoles(usersToCreate, rolesToCreate); + }); + it(`role(s) ${secReadCasesAllUser.roles.join()} shows error when user does not have permissions`, () => { + loginWithUserAndWaitForPageWithoutDateRange(HOSTS_URL, secReadCasesAllUser); + cy.get(TOASTER).should('have.text', 'Write role required to generate data'); + }); }); + // Originially written in December 2020, flakey from day1 + // has always been skipped with intentions to fix, see note at top of file + describe.skip('Default scope', () => { + beforeEach(() => { + cy.clearLocalStorage(); + loginAndWaitForPage(HOSTS_URL); + }); - describe('Default scope', () => { it('has SIEM index patterns selected on initial load', () => { openSourcerer(); isSourcererSelection(`auditbeat-*`); @@ -52,7 +69,7 @@ describe.skip('Sourcerer', () => { isSourcererOptions([`metrics-*`, `logs-*`]); }); - it('selected KIP gets added to sourcerer', () => { + it('selected DATA_VIEW gets added to sourcerer', () => { setSourcererOption(`metrics-*`); openSourcerer(); isSourcererSelection(`metrics-*`); @@ -75,8 +92,14 @@ describe.skip('Sourcerer', () => { isNotSourcererSelection(`metrics-*`); }); }); + // Originially written in December 2020, flakey from day1 + // has always been skipped with intentions to fix + describe.skip('Timeline scope', () => { + beforeEach(() => { + cy.clearLocalStorage(); + loginAndWaitForPage(HOSTS_URL); + }); - describe('Timeline scope', () => { const alertPatterns = ['.siem-signals-default']; const rawPatterns = ['auditbeat-*']; const allPatterns = [...alertPatterns, ...rawPatterns]; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 803ff4b4d0d80..033a12dd9de3e 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -9,6 +9,7 @@ import { ALERT_FLYOUT, CELL_TEXT, JSON_TEXT, TABLE_ROWS } from '../../screens/al import { expandFirstAlert, + refreshAlerts, waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded, } from '../../tasks/alerts'; @@ -32,6 +33,7 @@ describe('Alert details with unmapped fields', () => { createCustomRuleActivated(getUnmappedRule()); loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); + refreshAlerts(); expandFirstAlert(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts index 10f556a11bf60..171d224cc32d3 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts @@ -70,7 +70,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { ALERTS_URL } from '../../urls/navigation'; -describe.skip('Detection rules, EQL', () => { +describe('Detection rules, EQL', () => { const expectedUrls = getEqlRule().referenceUrls.join(''); const expectedFalsePositives = getEqlRule().falsePositivesExamples.join(''); const expectedTags = getEqlRule().tags.join(''); @@ -169,7 +169,7 @@ describe.skip('Detection rules, EQL', () => { }); }); -describe.skip('Detection rules, sequence EQL', () => { +describe('Detection rules, sequence EQL', () => { const expectedNumberOfRules = 1; const expectedNumberOfSequenceAlerts = '1 alert'; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index 02621ea49e906..378de8f0bc593 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -114,7 +114,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { goBackToAllRulesTable } from '../../tasks/rule_details'; import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation'; -import { DEFAULT_THREAT_MATCH_QUERY } from '../../../common/constants'; +const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d"'; describe('indicator match', () => { describe('Detection rules, Indicator Match', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts index ef3d3a82d40bd..92f9e8180d50c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts @@ -34,7 +34,6 @@ import { waitForRuleToChangeStatus, } from '../../tasks/alerts_detection_rules'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../../common/constants'; import { ALERTS_URL } from '../../urls/navigation'; import { createCustomRule } from '../../tasks/api_calls/rules'; @@ -46,6 +45,8 @@ import { getNewThresholdRule, } from '../../objects/rule'; +const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; + describe('Alerts detection rules', () => { beforeEach(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts index 89a0d5a660b97..f9d78ba12a5ea 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts @@ -98,7 +98,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery); cy.url().should( 'include', - 'app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + 'app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -106,7 +106,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); cy.url().should( 'include', - '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -114,7 +114,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery); cy.url().should( 'include', - 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -122,7 +122,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery); cy.url().should( 'include', - '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -130,15 +130,16 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery); cy.url().should( 'include', - '/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); it('redirects from a $ip$ with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery); + cy.url().should( 'include', - '/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + `/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))` ); }); @@ -146,7 +147,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery); cy.url().should( 'include', - '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -154,7 +155,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable); cy.url().should( 'include', - '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -162,7 +163,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery); cy.url().should( 'include', - '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -170,7 +171,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -178,7 +179,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -186,7 +187,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -194,7 +195,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts index fb41aec91b6c4..cbff911e5d982 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts @@ -121,7 +121,6 @@ describe('Create a timeline from a template', () => { loginAndWaitForPageWithoutDateRange(TIMELINE_TEMPLATES_URL); waitForTimelinesPanelToBeLoaded(); }); - it('Should have the same query and open the timeline modal', () => { selectCustomTemplates(); cy.wait('@timeline', { timeout: 100000 }); @@ -132,5 +131,6 @@ describe('Create a timeline from a template', () => { cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible'); cy.get(TIMELINE_DESCRIPTION).should('have.text', getTimeline().description); cy.get(TIMELINE_QUERY).should('have.text', getTimeline().query); + closeTimeline(); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts index 73eb141f1ce3d..28fe1294e6f01 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts @@ -182,11 +182,10 @@ describe('url state', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url); kqlSearch('source.ip: "10.142.0.9" {enter}'); navigateFromHeaderTo(HOSTS); - cy.get(NETWORK).should( 'have.attr', 'href', - `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))` + `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))` ); }); @@ -199,12 +198,12 @@ describe('url state', () => { cy.get(HOSTS).should( 'have.attr', 'href', - `/app/security/hosts?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')))` + `/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('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(NETWORK).should( 'have.attr', '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')))` + `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('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().should('have.text', 'siem-kibana'); @@ -215,21 +214,21 @@ describe('url state', () => { cy.get(ANOMALIES_TAB).should( 'have.attr', 'href', - "/app/security/hosts/siem-kibana/anomalies?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')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + "/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('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')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" ); cy.get(BREADCRUMBS) .eq(1) .should( 'have.attr', 'href', - `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%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')))` + `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('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(BREADCRUMBS) .eq(2) .should( 'have.attr', 'href', - `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%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')))` + `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('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')))` ); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts index f3d9bc1b9ef1a..70b8c1b400d51 100644 --- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts @@ -87,6 +87,7 @@ export const expectedExportedTimelineTemplate = ( }, }, }, + dataViewId: timelineTemplateBody.dataViewId, dateRange: { start: timelineTemplateBody.dateRange?.start, end: timelineTemplateBody.dateRange?.end, @@ -127,6 +128,7 @@ export const expectedExportedTimeline = (timelineResponse: Cypress.Response