diff --git a/catalog-info.yaml b/catalog-info.yaml index 96b4c146590a6..dbdc1dc24aa03 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -54,19 +54,9 @@ spec: provider_settings: trigger_mode: none teams: - kibana-release-operators: - access_level: MANAGE_BUILD_AND_READ kibana-operations: access_level: MANAGE_BUILD_AND_READ - appex-qa: - access_level: BUILD_AND_READ - security-engineering-productivity: - access_level: BUILD_AND_READ - fleet: - access_level: BUILD_AND_READ - kibana-tech-leads: - access_level: BUILD_AND_READ - kibana-core: + kibana-release-operators: access_level: BUILD_AND_READ cloud-tooling: access_level: BUILD_AND_READ diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx index 30fe35be0b551..80f108c8be784 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -62,6 +62,17 @@ const getHeaderCss = ({ size }: EuiThemeComputed) => ({ top: 2px; `, }, + leftHeaderSection: css` + // needed to enable breadcrumbs truncation + min-width: 0; + flex-shrink: 1; + `, + breadcrumbsSectionItem: css` + min-width: 0; // needed to enable breadcrumbs truncation + `, + redirectAppLinksContainer: css` + min-width: 0; // needed to enable breadcrumbs truncation + `, }); type HeaderCss = ReturnType; @@ -181,7 +192,7 @@ export const ProjectHeader = ({
- + {children} @@ -196,8 +207,11 @@ export const ProjectHeader = ({ /> - - + + diff --git a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx index 96e80af246511..3705b5885f1ab 100644 --- a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx +++ b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx @@ -29,6 +29,7 @@ export const RedirectAppLinks: FC = ({ children, navigateToUrl, currentAppId, + ...containerProps }) => { const containerRef = useRef(null); @@ -50,6 +51,7 @@ export const RedirectAppLinks: FC = ({ ref={containerRef} css={redirectAppLinksStyles} data-test-subj="kbnRedirectAppLink" + {...containerProps} > {children}
diff --git a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx index 9a069881b2128..da227ab023cbb 100644 --- a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx +++ b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx @@ -7,6 +7,7 @@ */ import React, { FC } from 'react'; +import type { RedirectAppLinksComponentProps } from '@kbn/shared-ux-link-redirect-app-types'; import { useServices } from './services'; import { RedirectAppLinks as Component } from './redirect_app_links.component'; @@ -22,6 +23,11 @@ import { RedirectAppLinks as Component } from './redirect_app_links.component'; * * ``` */ -export const RedirectAppLinks: FC<{}> = ({ children }) => ( - {children} +export const RedirectAppLinks: FC> = ({ + children, + ...props +}) => ( + + {children} + ); diff --git a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx index 2909dcdbda17d..89d8a61531e9b 100644 --- a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx +++ b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx @@ -25,10 +25,11 @@ const isKibanaContract = (services: any): services is RedirectAppLinksKibanaDepe * with which consumers can wrap their components or solutions. */ export const RedirectAppLinks: FC = ({ children, ...props }) => { - const container = {children}; - if (isKibanaContract(props)) { - const { coreStart } = props; + const { coreStart, ...containerProps } = props; + const container = ( + {children} + ); return ( {container} @@ -36,7 +37,10 @@ export const RedirectAppLinks: FC = ({ children, ...props ); } - const { navigateToUrl, currentAppId } = props; + const { navigateToUrl, currentAppId, ...containerProps } = props; + const container = ( + {children} + ); return ( {container} diff --git a/packages/shared-ux/link/redirect_app/types/index.d.ts b/packages/shared-ux/link/redirect_app/types/index.d.ts index 186e86af89435..01899edea65d3 100644 --- a/packages/shared-ux/link/redirect_app/types/index.d.ts +++ b/packages/shared-ux/link/redirect_app/types/index.d.ts @@ -32,7 +32,11 @@ export interface RedirectAppLinksKibanaDependencies { } /** Props for the `RedirectAppLinks` component. */ -export type RedirectAppLinksProps = RedirectAppLinksServices | RedirectAppLinksKibanaDependencies; +export type RedirectAppLinksProps = ( + | RedirectAppLinksServices + | RedirectAppLinksKibanaDependencies +) & + DetailedHTMLProps, HTMLDivElement>; /** Props for the `RedirectAppLinksComponent`. */ export interface RedirectAppLinksComponentProps diff --git a/src/plugins/telemetry/server/fetcher.test.ts b/src/plugins/telemetry/server/fetcher.test.ts index e1da374dc7239..5a61de1a0d83e 100644 --- a/src/plugins/telemetry/server/fetcher.test.ts +++ b/src/plugins/telemetry/server/fetcher.test.ts @@ -195,6 +195,61 @@ describe('FetcherTask', () => { }) ); + test( + 'Retries when `getCurrentConfigs` rejects', + fakeSchedulers(async (advance) => { + expect(fetcherTask['isOnline$'].value).toBe(false); + getCurrentConfigs.mockRejectedValueOnce(new Error('SomeError')).mockResolvedValue({ + telemetryOptIn: true, + telemetrySendUsageFrom: 'server', + failureCount: 0, + telemetryUrl: 'test-url', + }); + fetchMock.mockResolvedValue({}); + + const subscription = fetcherTask['validateConnectivity'](); + + // need to advance / await twice for retry + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the initial promise to fulfill + advance(1 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the retry promise to fulfill + + expect(getCurrentConfigs).toHaveBeenCalledTimes(2); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(updateReportFailure).toHaveBeenCalledTimes(0); + expect(fetcherTask['isOnline$'].value).toBe(true); + + subscription.unsubscribe(); + }) + ); + + test( + 'logs a message when retries are exceeded', + fakeSchedulers(async (advance) => { + expect(fetcherTask['isOnline$'].value).toBe(false); + getCurrentConfigs.mockRejectedValue(new Error('SomeError')); + + const subscription = fetcherTask['validateConnectivity'](); + + const wait = async () => { + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the initial promise to fulfill + }; + + for (let i = 0; i < 7; i++) { + await wait(); + } + + expect(fetcherTask['logger'].error).toBeCalledTimes(1); + expect(fetcherTask['logger'].error).toHaveBeenCalledWith( + `Cannot get the current config: SomeError` + ); + + subscription.unsubscribe(); + }) + ); + test( 'Should not retry if it hit the max number of failures for this version', fakeSchedulers(async (advance) => { diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index a79e043551775..756c9f49ded62 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -18,6 +18,10 @@ import { Subscription, takeUntil, timer, + catchError, + defer, + EMPTY, + retry, } from 'rxjs'; import fetch from 'node-fetch'; import type { TelemetryCollectionManagerPluginStart } from '@kbn/telemetry-collection-manager-plugin/server'; @@ -105,7 +109,25 @@ export class FetcherTask { // Skip any further processing if already online filter(() => !this.isOnline$.value), // Fetch current state and configs - exhaustMap(async () => await this.getCurrentConfigs()), + exhaustMap(() => { + return defer(() => { + return this.getCurrentConfigs(); + }).pipe( + // exp-backoff retries in case of errors fetching the config + retry({ + count: 5, + delay: (error, retryIndex) => { + const retryDelay = 1000 * Math.min(Math.pow(2, retryIndex + 2), 64); // 5 retries -> 8s, 16s, 32s, 64s, 64s + return timer(retryDelay); + }, + }), + // shallow errors if all retry failed, next time tick will continue the emission + catchError((err) => { + this.logger.error(`Cannot get the current config: ${err.message}`); + return EMPTY; + }) + ); + }), // Skip if opted-out, or should only send from the browser filter( ({ telemetryOptIn, telemetrySendUsageFrom }) => diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index e1565f1ff6b0d..8c35ed21568bb 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -215,7 +215,7 @@ export function ChangeDataView({ })} ) : ( - + ), ); @@ -336,7 +336,7 @@ export function ChangeDataView({ if (textBasedLanguages?.length) { panelItems.push( , - + void; - fail: () => void; - complete: () => void; - transform: Transform; -} - -function createStreamMock(): StreamMock { +function createStreamMock() { const transform: Transform = new Transform({}); return { - write: (data: string) => { - transform.push(`${data}\n`); + write: (data: unknown) => { + transform.push(data); }, fail: () => { transform.emit('error', new Error('Stream failed')); @@ -34,7 +29,10 @@ function createStreamMock(): StreamMock { } const logger = loggerMock.create(); describe('getTokenCountFromInvokeStream', () => { - let stream: StreamMock; + beforeEach(() => { + jest.resetAllMocks(); + }); + let stream: ReturnType; const body = { messages: [ { @@ -48,36 +46,79 @@ describe('getTokenCountFromInvokeStream', () => { ], }; + const chunk = { + object: 'chat.completion.chunk', + choices: [ + { + delta: { + content: 'Single.', + }, + }, + ], + }; + const PROMPT_TOKEN_COUNT = 34; const COMPLETION_TOKEN_COUNT = 2; + describe('OpenAI stream', () => { + beforeEach(() => { + stream = createStreamMock(); + stream.write(`data: ${JSON.stringify(chunk)}`); + }); - beforeEach(() => { - stream = createStreamMock(); - stream.write('Single'); - }); - - describe('when a stream completes', () => { - beforeEach(async () => { + it('counts the prompt + completion tokens for OpenAI response', async () => { stream.complete(); - }); - it('counts the prompt tokens', async () => { const tokens = await getTokenCountFromInvokeStream({ responseStream: stream.transform, body, logger, + actionTypeId: '.gen-ai', }); expect(tokens.prompt).toBe(PROMPT_TOKEN_COUNT); expect(tokens.completion).toBe(COMPLETION_TOKEN_COUNT); expect(tokens.total).toBe(PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT); }); + it('resolves the promise with the correct prompt tokens', async () => { + const tokenPromise = getTokenCountFromInvokeStream({ + responseStream: stream.transform, + body, + logger, + actionTypeId: '.gen-ai', + }); + + stream.fail(); + + await expect(tokenPromise).resolves.toEqual({ + prompt: PROMPT_TOKEN_COUNT, + total: PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT, + completion: COMPLETION_TOKEN_COUNT, + }); + expect(logger.error).toHaveBeenCalled(); + }); }); + describe('Bedrock stream', () => { + beforeEach(() => { + stream = createStreamMock(); + stream.write(encodeBedrockResponse('Simple.')); + }); - describe('when a stream fails', () => { + it('counts the prompt + completion tokens for OpenAI response', async () => { + stream.complete(); + const tokens = await getTokenCountFromInvokeStream({ + responseStream: stream.transform, + body, + logger, + actionTypeId: '.bedrock', + }); + expect(tokens.prompt).toBe(PROMPT_TOKEN_COUNT); + expect(tokens.completion).toBe(COMPLETION_TOKEN_COUNT); + expect(tokens.total).toBe(PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT); + }); it('resolves the promise with the correct prompt tokens', async () => { const tokenPromise = getTokenCountFromInvokeStream({ responseStream: stream.transform, body, logger, + actionTypeId: '.bedrock', }); stream.fail(); @@ -91,3 +132,16 @@ describe('getTokenCountFromInvokeStream', () => { }); }); }); + +function encodeBedrockResponse(completion: string) { + return new EventStreamCodec(toUtf8, fromUtf8).encode({ + headers: {}, + body: Uint8Array.from( + Buffer.from( + JSON.stringify({ + bytes: Buffer.from(JSON.stringify({ completion })).toString('base64'), + }) + ) + ), + }); +} diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts index 594fec89d93c0..dfb4bae69f8cf 100644 --- a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts @@ -9,6 +9,8 @@ import { Logger } from '@kbn/logging'; import { encode } from 'gpt-tokenizer'; import { Readable } from 'stream'; import { finished } from 'stream/promises'; +import { EventStreamCodec } from '@smithy/eventstream-codec'; +import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; export interface InvokeBody { messages: Array<{ @@ -26,10 +28,12 @@ export interface InvokeBody { * @param logger the logger */ export async function getTokenCountFromInvokeStream({ + actionTypeId, responseStream, body, logger, }: { + actionTypeId: string; responseStream: Readable; body: InvokeBody; logger: Logger; @@ -47,9 +51,37 @@ export async function getTokenCountFromInvokeStream({ .join('\n') ).length; - let responseBody: string = ''; + const parser = actionTypeId === '.bedrock' ? parseBedrockStream : parseOpenAIStream; + const parsedResponse = await parser(responseStream, logger); + + const completionTokens = encode(parsedResponse).length; + return { + prompt: promptTokens, + completion: completionTokens, + total: promptTokens + completionTokens, + }; +} + +type StreamParser = (responseStream: Readable, logger: Logger) => Promise; - responseStream.on('data', (chunk: string) => { +const parseBedrockStream: StreamParser = async (responseStream, logger) => { + const responseBuffer: Uint8Array[] = []; + responseStream.on('data', (chunk) => { + // special encoding for bedrock, do not attempt to convert to string + responseBuffer.push(chunk); + }); + try { + await finished(responseStream); + } catch (e) { + logger.error('An error occurred while calculating streaming response tokens'); + } + return parseBedrockBuffer(responseBuffer); +}; + +const parseOpenAIStream: StreamParser = async (responseStream, logger) => { + let responseBody: string = ''; + responseStream.on('data', (chunk) => { + // no special encoding, can safely use toString and append to responseBody responseBody += chunk.toString(); }); try { @@ -57,12 +89,109 @@ export async function getTokenCountFromInvokeStream({ } catch (e) { logger.error('An error occurred while calculating streaming response tokens'); } + return parseOpenAIResponse(responseBody); +}; - const completionTokens = encode(responseBody).length; +/** + * Parses a Bedrock buffer from an array of chunks. + * + * @param {Uint8Array[]} chunks - Array of Uint8Array chunks to be parsed. + * @returns {string} - Parsed string from the Bedrock buffer. + */ +const parseBedrockBuffer = (chunks: Uint8Array[]): string => { + // Initialize an empty Uint8Array to store the concatenated buffer. + let bedrockBuffer: Uint8Array = new Uint8Array(0); - return { - prompt: promptTokens, - completion: completionTokens, - total: promptTokens + completionTokens, - }; + // Map through each chunk to process the Bedrock buffer. + return chunks + .map((chunk) => { + // Concatenate the current chunk to the existing buffer. + bedrockBuffer = concatChunks(bedrockBuffer, chunk); + // Get the length of the next message in the buffer. + let messageLength = getMessageLength(bedrockBuffer); + // Initialize an array to store fully formed message chunks. + const buildChunks = []; + // Process the buffer until no complete messages are left. + while (bedrockBuffer.byteLength > 0 && bedrockBuffer.byteLength >= messageLength) { + // Extract a chunk of the specified length from the buffer. + const extractedChunk = bedrockBuffer.slice(0, messageLength); + // Add the extracted chunk to the array of fully formed message chunks. + buildChunks.push(extractedChunk); + // Remove the processed chunk from the buffer. + bedrockBuffer = bedrockBuffer.slice(messageLength); + // Get the length of the next message in the updated buffer. + messageLength = getMessageLength(bedrockBuffer); + } + + const awsDecoder = new EventStreamCodec(toUtf8, fromUtf8); + + // Decode and parse each message chunk, extracting the 'completion' property. + return buildChunks + .map((bChunk) => { + const event = awsDecoder.decode(bChunk); + const body = JSON.parse( + Buffer.from(JSON.parse(new TextDecoder().decode(event.body)).bytes, 'base64').toString() + ); + return body.completion; + }) + .join(''); + }) + .join(''); +}; + +/** + * Concatenates two Uint8Array buffers. + * + * @param {Uint8Array} a - First buffer. + * @param {Uint8Array} b - Second buffer. + * @returns {Uint8Array} - Concatenated buffer. + */ +function concatChunks(a: Uint8Array, b: Uint8Array): Uint8Array { + const newBuffer = new Uint8Array(a.length + b.length); + // Copy the contents of the first buffer to the new buffer. + newBuffer.set(a); + // Copy the contents of the second buffer to the new buffer starting from the end of the first buffer. + newBuffer.set(b, a.length); + return newBuffer; +} + +/** + * Gets the length of the next message from the buffer. + * + * @param {Uint8Array} buffer - Buffer containing the message. + * @returns {number} - Length of the next message. + */ +function getMessageLength(buffer: Uint8Array): number { + // If the buffer is empty, return 0. + if (buffer.byteLength === 0) return 0; + // Create a DataView to read the Uint32 value at the beginning of the buffer. + const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + // Read and return the Uint32 value (message length). + return view.getUint32(0, false); } + +const parseOpenAIResponse = (responseBody: string) => + responseBody + .split('\n') + .filter((line) => { + return line.startsWith('data: ') && !line.endsWith('[DONE]'); + }) + .map((line) => { + return JSON.parse(line.replace('data: ', '')); + }) + .filter( + ( + line + ): line is { + choices: Array<{ + delta: { content?: string; function_call?: { name?: string; arguments: string } }; + }>; + } => { + return 'object' in line && line.object === 'chat.completion.chunk'; + } + ) + .reduce((prev, line) => { + const msg = line.choices[0].delta!; + prev += msg.content || ''; + return prev; + }, ''); diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts index a41f3d71bc6bc..efe9630a5f238 100644 --- a/x-pack/plugins/index_management/common/constants/index.ts +++ b/x-pack/plugins/index_management/common/constants/index.ts @@ -10,6 +10,10 @@ export { API_BASE_PATH, INTERNAL_API_BASE_PATH } from './api_base_path'; export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters'; export * from './index_statuses'; +// Since each index can have a max length or 255 characters and the max length of +// the request is 4096 bytes we can fit a max of 16 indices in a single request. +export const MAX_INDICES_PER_REQUEST = 16; + export { UIM_APP_NAME, UIM_APP_LOAD, diff --git a/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js b/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js index 09c4988fc27b1..f27c184afea68 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js @@ -27,8 +27,9 @@ export const clearCacheIndices = dispatch(reloadIndices(indexNames)); notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.clearCacheIndicesAction.successMessage', { - defaultMessage: 'Successfully cleared cache: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: + 'Successfully cleared cache for {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, }) ); }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/close_indices.js b/x-pack/plugins/index_management/public/application/store/actions/close_indices.js index a9bedef283ec4..368298a67137e 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/close_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/close_indices.js @@ -25,8 +25,8 @@ export const closeIndices = dispatch(reloadIndices(indexNames)); notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.closeIndicesAction.successfullyClosedIndicesMessage', { - defaultMessage: 'Successfully closed: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: 'Successfully closed {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, }) ); }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js b/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js index 2a2f8f0b41092..1b082594f085a 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js @@ -23,8 +23,8 @@ export const deleteIndices = } notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.deleteIndicesAction.successfullyDeletedIndicesMessage', { - defaultMessage: 'Successfully deleted: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: 'Successfully deleted {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, }) ); dispatch(deleteIndicesSuccess({ indexNames })); diff --git a/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js b/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js index 83d4d5d46a3ae..d033f5fbe343e 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js @@ -26,8 +26,8 @@ export const flushIndices = dispatch(reloadIndices(indexNames)); notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.flushIndicesAction.successfullyFlushedIndicesMessage', { - defaultMessage: 'Successfully flushed: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: 'Successfully flushed {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, }) ); }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js b/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js index bb3af529f404a..d589ff6dd780f 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js @@ -28,8 +28,9 @@ export const forcemergeIndices = i18n.translate( 'xpack.idxMgmt.forceMergeIndicesAction.successfullyForceMergedIndicesMessage', { - defaultMessage: 'Successfully force merged: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: + 'Successfully force merged {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, } ) ); diff --git a/x-pack/plugins/index_management/public/application/store/actions/open_indices.js b/x-pack/plugins/index_management/public/application/store/actions/open_indices.js index 8d4d0f728d160..53bb9186c2f94 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/open_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/open_indices.js @@ -26,8 +26,8 @@ export const openIndices = dispatch(reloadIndices(indexNames)); notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.openIndicesAction.successfullyOpenedIndicesMessage', { - defaultMessage: 'Successfully opened: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: 'Successfully opened {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, }) ); }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js b/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js index 56fe892393ea4..574aa18c0282c 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js @@ -26,8 +26,8 @@ export const refreshIndices = dispatch(reloadIndices(indexNames)); notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.refreshIndicesAction.successfullyRefreshedIndicesMessage', { - defaultMessage: 'Successfully refreshed: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, + defaultMessage: 'Successfully refreshed {count, plural, one {# index} other {# indices} }', + values: { count: indexNames.length }, }) ); }; diff --git a/x-pack/plugins/index_management/server/routes/api/indices/helpers.test.ts b/x-pack/plugins/index_management/server/routes/api/indices/helpers.test.ts new file mode 100644 index 0000000000000..fd44742d26e4c --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/indices/helpers.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient } from '@kbn/core/server'; + +import { executeAsyncByChunks } from './helpers'; + +const generateIndices = (count: number) => { + const indices = []; + + for (let i = 0; i < count; i++) { + indices.push(`index-${i}`); + } + + return indices; +}; + +const mockClient = { + asCurrentUser: { + indices: { + delete: jest.fn(), + }, + }, +} as unknown as IScopedClusterClient; + +describe('executeAsyncByChunks', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should make just one request for one index', async () => { + const params = { + index: generateIndices(1), + }; + + await executeAsyncByChunks(params, mockClient, 'delete'); + + expect(mockClient.asCurrentUser.indices.delete).toHaveBeenCalledTimes(1); + }); + + it('should make 2 requests for 32 indices', async () => { + const params = { + index: generateIndices(32), + }; + + await executeAsyncByChunks(params, mockClient, 'delete'); + + expect(mockClient.asCurrentUser.indices.delete).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/helpers.ts b/x-pack/plugins/index_management/server/routes/api/indices/helpers.ts new file mode 100644 index 0000000000000..bb04cbd2c15c8 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/indices/helpers.ts @@ -0,0 +1,53 @@ +/* + * 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 { chunk } from 'lodash'; + +import type { IScopedClusterClient } from '@kbn/core/server'; +import { MAX_INDICES_PER_REQUEST } from '../../../../common/constants'; + +// To avoid having to to match method signatures with the client +// type, we use a generic CallableFn type. +type CallableFn = (args: Record) => Promise; + +export async function executeAsyncByChunks( + // Since we are using a key to access the index method, we need + // to use a generic type. + params: { + index: T[]; + format?: string; + expand_wildcards?: string; + max_num_segments?: number; + }, + dataClient: IScopedClusterClient, + methodName: keyof IScopedClusterClient['asCurrentUser']['indices'] +) { + const { index: indices, ...commonParams } = params; + + // When the number of indices is small, we can execute in a single request + // + // Otherwise we need to split the indices into chunks and execute them in multiple requests because + // if we try to execute an action with too many indices that account for a long string in the request + // ES will throw an error saying that the HTTP line is too large. + if (indices.length <= MAX_INDICES_PER_REQUEST) { + await (dataClient.asCurrentUser.indices[methodName] as CallableFn)({ + ...commonParams, + index: indices, + }); + } else { + const chunks = chunk(indices, MAX_INDICES_PER_REQUEST); + + await Promise.all( + chunks.map((chunkOfIndices) => + (dataClient.asCurrentUser.indices[methodName] as CallableFn)({ + ...commonParams, + index: chunkOfIndices, + }) + ) + ); + } +} diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts index a46a23b8fe479..bfedf6f4cb0cf 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -28,7 +29,8 @@ export function registerClearCacheRoute({ router, lib: { handleEsError } }: Rout }; try { - await client.asCurrentUser.indices.clearCache(params); + await executeAsyncByChunks(params, client, 'clearCache'); + return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts index 69d33b7fc7999..b83c781f6457d 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -28,7 +29,7 @@ export function registerCloseRoute({ router, lib: { handleEsError } }: RouteDepe }; try { - await client.asCurrentUser.indices.close(params); + await executeAsyncByChunks(params, client, 'close'); return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts index b72a2059beb1d..b3931c1d56172 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -22,13 +23,14 @@ export function registerDeleteRoute({ router, lib: { handleEsError } }: RouteDep const { indices = [] } = request.body as typeof bodySchema.type; const params = { - expand_wildcards: 'none' as const, format: 'json', + expand_wildcards: 'none' as const, index: indices, }; try { - await client.asCurrentUser.indices.delete(params); + await executeAsyncByChunks(params, client, 'delete'); + return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts index cc07b92e70907..6ba8000306fec 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -28,7 +29,8 @@ export function registerFlushRoute({ router, lib: { handleEsError } }: RouteDepe }; try { - await client.asCurrentUser.indices.flush(params); + await executeAsyncByChunks(params, client, 'flush'); + return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts index af07a7371cf65..ffbe50598f197 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -36,7 +37,8 @@ export function registerForcemergeRoute({ router, lib: { handleEsError } }: Rout } try { - await client.asCurrentUser.indices.forcemerge(params); + await executeAsyncByChunks(params, client, 'forcemerge'); + return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts index dde9e72af39d7..9d0ae0a44b4ec 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -28,7 +29,8 @@ export function registerOpenRoute({ router, lib: { handleEsError } }: RouteDepen }; try { - await client.asCurrentUser.indices.open(params); + await executeAsyncByChunks(params, client, 'open'); + return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts index 2483cd534b80e..c414a73cd73c1 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; +import { executeAsyncByChunks } from './helpers'; const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), @@ -28,7 +29,8 @@ export function registerRefreshRoute({ router, lib: { handleEsError } }: RouteDe }; try { - await client.asCurrentUser.indices.refresh(params); + await executeAsyncByChunks(params, client, 'refresh'); + return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts index 91a04187fc238..d64c6b1013d66 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts @@ -5,8 +5,10 @@ * 2.0. */ +import { chunk } from 'lodash'; import { schema } from '@kbn/config-schema'; +import { MAX_INDICES_PER_REQUEST } from '../../../../common/constants'; import { RouteDependencies } from '../../../types'; import { fetchIndices } from '../../../lib/fetch_indices'; import { addBasePath } from '..'; @@ -30,7 +32,27 @@ export function registerReloadRoute({ const { indexNames = [] } = (request.body as typeof bodySchema.type) ?? {}; try { - const indices = await fetchIndices({ client, indexDataEnricher, config, indexNames }); + let indices; + + // When the number of indices is small, we can execute in a single request + // + // Otherwise we need to split the indices into chunks and execute them in multiple requests because + // if we try to execute an action with too many indices that account for a long string in the request + // ES will throw an error saying that the HTTP line is too large. + if (indexNames.length <= MAX_INDICES_PER_REQUEST) { + indices = await fetchIndices({ client, indexDataEnricher, config, indexNames }); + } else { + const chunks = chunk(indexNames, MAX_INDICES_PER_REQUEST); + + indices = ( + await Promise.all( + chunks.map((indexNamesChunk) => + fetchIndices({ client, indexDataEnricher, config, indexNames: indexNamesChunk }) + ) + ) + ).flat(); + } + return response.ok({ body: indices }); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx index 74d65d73f23b8..e74ee0a4b8130 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx @@ -7,7 +7,10 @@ import React from 'react'; import { FlyoutContentActions } from '@kbn/discover-plugin/public'; import { DataTableRecord } from '@kbn/discover-utils/src/types'; +import { AgentIcon, CloudProvider, CloudProviderIcon } from '@kbn/custom-icons'; import { useMeasure } from 'react-use/lib'; +import { AgentName } from '@kbn/elastic-agent-utils'; +import { first } from 'lodash'; import { FlyoutDoc } from './types'; import * as constants from '../../../common/constants'; import { HighlightField } from './sub_components/highlight_field'; @@ -47,156 +50,170 @@ export function FlyoutHighlights({ }) { const [ref, dimensions] = useMeasure(); const { columns, fieldWidth } = useFlyoutColumnWidth(dimensions.width); + return ( + {/* Service highlight */} {formattedDoc[constants.SERVICE_NAME_FIELD] && ( + } + label={flyoutServiceLabel} + value={flattenedDoc[constants.SERVICE_NAME_FIELD]} width={fieldWidth} /> )} {formattedDoc[constants.TRACE_ID_FIELD] && ( )} - + {/* Infrastructure highlight */} {formattedDoc[constants.HOST_NAME_FIELD] && ( )} {formattedDoc[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD] && ( )} {formattedDoc[constants.ORCHESTRATOR_RESOURCE_ID_FIELD] && ( )} - + {/* Cloud highlight */} {formattedDoc[constants.CLOUD_PROVIDER_FIELD] && ( + } + label={flyoutCloudProviderLabel} + value={flattenedDoc[constants.CLOUD_PROVIDER_FIELD]} width={fieldWidth} /> )} {formattedDoc[constants.CLOUD_REGION_FIELD] && ( )} {formattedDoc[constants.CLOUD_AVAILABILITY_ZONE_FIELD] && ( )} {formattedDoc[constants.CLOUD_PROJECT_ID_FIELD] && ( )} {formattedDoc[constants.CLOUD_INSTANCE_ID_FIELD] && ( )} - + {/* Other highlights */} {formattedDoc[constants.LOG_FILE_PATH_FIELD] && ( )} {formattedDoc[constants.DATASTREAM_NAMESPACE_FIELD] && ( )} {formattedDoc[constants.DATASTREAM_DATASET_FIELD] && ( )} {formattedDoc[constants.AGENT_NAME_FIELD] && ( )} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx index ca47b10548236..1034a3015cc89 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiText, copyToClipboard } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, copyToClipboard, EuiTextTruncate } from '@elastic/eui'; import React, { ReactNode, useMemo, useState } from 'react'; import { HoverAction, HoverActionType } from './hover_action'; import { @@ -18,21 +18,22 @@ import { import { useDiscoverActionsContext } from '../../../hooks/use_discover_action'; interface HighlightFieldProps { - label: string | ReactNode; field: string; - value: unknown; formattedValue: string; - dataTestSubj: string; + icon?: ReactNode; + label: string | ReactNode; + value: unknown; width: number; } export function HighlightField({ - label, field, - value, formattedValue, - dataTestSubj, + icon, + label, + value, width, + ...props }: HighlightFieldProps) { const filterForText = flyoutHoverActionFilterForText(value); const filterOutText = flyoutHoverActionFilterOutText(value); @@ -89,14 +90,33 @@ export function HighlightField({ [filterForText, filterOutText, actions, field, value, columnAdded] ); return formattedValue ? ( - + {label} - + + + {icon && {icon}} + + + {(truncatedText: string) => ( + + )} + + + + ) : null; diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx index 5ed25be2b36d9..1b5883ed082e5 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx @@ -7,14 +7,7 @@ import React from 'react'; -import { - EuiFlexGroup, - EuiToolTip, - EuiButtonIcon, - useEuiTheme, - EuiTextTruncate, - EuiText, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiToolTip, EuiButtonIcon, useEuiTheme, EuiFlexItem } from '@elastic/eui'; import type { IconType } from '@elastic/eui'; export interface HoverActionType { @@ -26,12 +19,11 @@ export interface HoverActionType { } interface HoverActionProps { - displayText: string; + children: React.ReactNode; actions: HoverActionType[]; - width: number; } -export const HoverAction = ({ displayText, actions, width }: HoverActionProps) => { +export const HoverAction = ({ children, actions }: HoverActionProps) => { const { euiTheme } = useEuiTheme(); return ( @@ -49,14 +41,7 @@ export const HoverAction = ({ displayText, actions, width }: HoverActionProps) = }, }} > - - {(truncatedText: string) => ( - - )} - + {children} - {i18n.translate('xpack.observability.slo.slo.activeAlertsBadge.label', { - defaultMessage: '{count, plural, one {# alert} other {# alerts}}', - values: { count: activeAlerts }, - })} + {viewMode !== 'default' + ? activeAlerts + : i18n.translate('xpack.observability.slo.slo.activeAlertsBadge.label', { + defaultMessage: '{count, plural, one {# alert} other {# alerts}}', + values: { count: activeAlerts }, + })} ); diff --git a/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx index 455d6d9d24ed3..f79b700ed9be5 100644 --- a/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx +++ b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx @@ -5,24 +5,25 @@ * 2.0. */ -import { EuiBadge, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiBadgeProps, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; -import { euiLightVars } from '@kbn/ui-theme'; import React from 'react'; +import { euiLightVars } from '@kbn/ui-theme'; export interface Props { + color?: EuiBadgeProps['color']; slo: SLOWithSummaryResponse; } -export function SloGroupByBadge({ slo }: Props) { +export function SloGroupByBadge({ slo, color }: Props) { if (!slo.groupBy || slo.groupBy === ALL_VALUE) { return null; } return ( - + { const location = await locator.getLocation({}); expect(location.app).toEqual('observability'); expect(location.path).toEqual( - "/slos?search=(kqlQuery:'',page:0,sort:(by:status,direction:desc))" + "/slos?search=(kqlQuery:'',page:0,sort:(by:status,direction:desc),viewMode:compact)" ); }); @@ -24,7 +24,7 @@ describe('SloListLocator', () => { }); expect(location.app).toEqual('observability'); expect(location.path).toEqual( - "/slos?search=(kqlQuery:'slo.name:%20%22Service%20Availability%22%20and%20slo.indicator.type%20:%20%22sli.kql.custom%22',page:0,sort:(by:status,direction:desc))" + "/slos?search=(kqlQuery:'slo.name:%20%22Service%20Availability%22%20and%20slo.indicator.type%20:%20%22sli.kql.custom%22',page:0,sort:(by:status,direction:desc),viewMode:compact)" ); }); }); diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx index 267ebac0ce9b4..67869e7f0e76e 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx @@ -11,7 +11,7 @@ import { ComponentStory } from '@storybook/react'; import { EuiFlexGroup } from '@elastic/eui'; import { buildForecastedSlo } from '../../../../data/slo/slo'; import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator'; -import { SloBadges as Component, Props } from './slo_badges'; +import { SloBadges as Component, SloBadgesProps } from './slo_badges'; export default { component: Component, @@ -19,7 +19,7 @@ export default { decorators: [KibanaReactStorybookDecorator], }; -const Template: ComponentStory = (props: Props) => ( +const Template: ComponentStory = (props: SloBadgesProps) => ( diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index deccd010205a0..9ff1e3c14a2b2 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -17,8 +17,9 @@ import { SloTimeWindowBadge } from './slo_time_window_badge'; import { SloRulesBadge } from './slo_rules_badge'; import type { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; import { SloGroupByBadge } from '../../../../components/slo/slo_status_badge/slo_group_by_badge'; +export type ViewMode = 'default' | 'compact'; -export interface Props { +export interface SloBadgesProps { activeAlerts?: number; isLoading: boolean; rules: Array> | undefined; @@ -26,33 +27,17 @@ export interface Props { onClickRuleBadge: () => void; } -export function SloBadges({ activeAlerts, isLoading, rules, slo, onClickRuleBadge }: Props) { +export function SloBadges({ + activeAlerts, + isLoading, + rules, + slo, + onClickRuleBadge, +}: SloBadgesProps) { return ( {isLoading ? ( - <> - - - - + ) : ( <> @@ -66,3 +51,31 @@ export function SloBadges({ activeAlerts, isLoading, rules, slo, onClickRuleBadg ); } + +export function LoadingBadges() { + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx index ad73af3d73bfa..c85eb6776680b 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx @@ -6,21 +6,22 @@ */ import React from 'react'; -import { EuiBadge, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiFlexItem, EuiToolTip, EuiBadgeProps } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; -import { euiLightVars } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; +import { euiLightVars } from '@kbn/ui-theme'; import { useKibana } from '../../../../utils/kibana_react'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; import { isApmIndicatorType } from '../../../../utils/slo/indicator'; import { toIndicatorTypeLabel } from '../../../../utils/slo/labels'; export interface Props { + color?: EuiBadgeProps['color']; slo: SLOWithSummaryResponse; } -export function SloIndicatorTypeBadge({ slo }: Props) { +export function SloIndicatorTypeBadge({ slo, color }: Props) { const { application: { navigateToUrl }, http: { basePath }, @@ -54,7 +55,7 @@ export function SloIndicatorTypeBadge({ slo }: Props) { return ( <> - + {toIndicatorTypeLabel(slo.indicator.type)} @@ -68,7 +69,7 @@ export function SloIndicatorTypeBadge({ slo }: Props) { })} > - + ); diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx index d218eeda7f0ed..b5d4ecd0224fe 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBadge, EuiFlexItem } from '@elastic/eui'; +import { EuiBadge, EuiBadgeProps, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { euiLightVars } from '@kbn/ui-theme'; @@ -15,16 +15,17 @@ import { toCalendarAlignedMomentUnitOfTime } from '../../../../utils/slo/duratio import { toDurationLabel } from '../../../../utils/slo/labels'; export interface Props { + color?: EuiBadgeProps['color']; slo: SLOWithSummaryResponse; } -export function SloTimeWindowBadge({ slo }: Props) { +export function SloTimeWindowBadge({ slo, color }: Props) { const unit = slo.timeWindow.duration.slice(-1); if (rollingTimeWindowTypeSchema.is(slo.timeWindow.type)) { return ( @@ -45,7 +46,7 @@ export function SloTimeWindowBadge({ slo }: Props) { return ( - + {i18n.translate('xpack.observability.slo.slo.timeWindow.calendar', { defaultMessage: '{elapsed}/{total} days', values: { diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/cards_per_row.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/cards_per_row.tsx new file mode 100644 index 0000000000000..04e787d7b3536 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/cards_per_row.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; + +export const SLO_CARD_VIEW_PER_ROW_SIZE = 'slo-card-view-per-row-size'; + +export function CardsPerRow({ + setCardsPerRow, +}: { + setCardsPerRow: (cardsPerRow?: string) => void; +}) { + const [value, setValue] = useLocalStorage(SLO_CARD_VIEW_PER_ROW_SIZE, '3'); + + useEffect(() => { + setCardsPerRow(value); + }, [setCardsPerRow, value]); + + const options = [ + { value: '3', text: '3' }, + { value: '4', text: '4' }, + ]; + + return ( + + } + > + setValue(e.target.value)} + /> + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item.tsx new file mode 100644 index 0000000000000..07ad3caf1fd00 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item.tsx @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + Chart, + isMetricElementEvent, + Metric, + Settings, + DARK_THEME, + MetricTrendShape, +} from '@elastic/charts'; +import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui'; +import { SLOWithSummaryResponse, HistoricalSummaryResponse, ALL_VALUE } from '@kbn/slo-schema'; +import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { useSloListActions } from '../../hooks/use_slo_list_actions'; +import { BurnRateRuleFlyout } from '../common/burn_rate_rule_flyout'; +import { formatHistoricalData } from '../../../../utils/slo/chart_data_formatter'; +import { useKibana } from '../../../../utils/kibana_react'; +import { useSloFormattedSummary } from '../../hooks/use_slo_summary'; +import { SloCardItemActions } from './slo_card_item_actions'; +import { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; +import { SloDeleteConfirmationModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; +import { SloCardItemBadges } from './slo_card_item_badges'; + +export interface Props { + slo: SLOWithSummaryResponse; + rules: Array> | undefined; + historicalSummary?: HistoricalSummaryResponse[]; + historicalSummaryLoading: boolean; + activeAlerts?: number; + loading: boolean; + error: boolean; + cardsPerRow: number; +} + +const useCardColor = (status?: SLOWithSummaryResponse['summary']['status']) => { + const colors = { + DEGRADING: useEuiBackgroundColor('warning'), + VIOLATED: useEuiBackgroundColor('danger'), + HEALTHY: useEuiBackgroundColor('success'), + NO_DATA: useEuiBackgroundColor('subdued'), + }; + + return colors[status ?? 'NO_DATA']; +}; + +const getSubTitle = (slo: SLOWithSummaryResponse, cardsPerRow: number) => { + const subTitle = + slo.groupBy && slo.groupBy !== ALL_VALUE ? `${slo.groupBy}: ${slo.instanceId}` : ''; + if (cardsPerRow === 4) { + return subTitle.substring(0, 30) + (subTitle.length > 30 ? '..' : ''); + } + return subTitle.substring(0, 40) + (subTitle.length > 40 ? '..' : ''); +}; + +export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, cardsPerRow }: Props) { + const { + application: { navigateToUrl }, + } = useKibana().services; + + const [isMouseOver, setIsMouseOver] = useState(false); + const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); + const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false); + const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); + + const { sliValue, sloTarget, sloDetailsUrl } = useSloFormattedSummary(slo); + + const cardColor = useCardColor(slo.summary.status); + + const subTitle = getSubTitle(slo, cardsPerRow); + + const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value'); + + const { handleCreateRule, handleDeleteCancel, handleDeleteConfirm } = useSloListActions({ + slo, + setDeleteConfirmationModalOpen, + setIsActionsPopoverOpen, + setIsAddRuleFlyoutOpen, + }); + + return ( + <> + { + if (!isMouseOver) { + setIsMouseOver(true); + } + }} + onMouseLeave={() => { + if (isMouseOver) { + setIsMouseOver(false); + } + }} + paddingSize="none" + style={{ + height: '182px', + overflow: 'hidden', + position: 'relative', + }} + title={slo.summary.status} + > + + { + if (isMetricElementEvent(d)) { + navigateToUrl(sloDetailsUrl); + } + }} + locale={i18n.getLocale()} + /> + ({ + x: d.key as number, + y: d.value as number, + })), + extra: ( + + ), + icon: () => , + color: cardColor, + }, + ], + ]} + /> + + + {(isMouseOver || isActionsPopoverOpen) && ( + + )} + + + + + {isDeleteConfirmationModalOpen ? ( + + ) : null} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_actions.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_actions.tsx new file mode 100644 index 0000000000000..51d1887d433fb --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_actions.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import styled from 'styled-components'; +import { useEuiShadow } from '@elastic/eui'; +import { SloItemActions } from '../slo_item_actions'; + +type PopoverPosition = 'relative' | 'default'; + +interface ActionContainerProps { + boxShadow: string; + position: PopoverPosition; +} + +const Container = styled.div` + ${({ position }) => + position === 'relative' + ? // custom styles used to overlay the popover button on `MetricItem` + ` + display: inline-block; + position: relative; + bottom: 42px; + left: 12px; + z-index: 1; +` + : // otherwise, no custom position needed + ''} + + border-radius: ${({ theme }) => theme.eui.euiBorderRadius}; + ${({ boxShadow, position }) => (position === 'relative' ? boxShadow : '')} +`; + +interface Props { + slo: SLOWithSummaryResponse; + isActionsPopoverOpen: boolean; + setIsActionsPopoverOpen: (value: boolean) => void; + setDeleteConfirmationModalOpen: (value: boolean) => void; + setIsAddRuleFlyoutOpen: (value: boolean) => void; +} + +export function SloCardItemActions(props: Props) { + const euiShadow = useEuiShadow('l'); + + return ( + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx new file mode 100644 index 0000000000000..06bc6cf19e0c9 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx @@ -0,0 +1,60 @@ +/* + * 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 { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; +import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import styled from 'styled-components'; +import { EuiFlexGroup } from '@elastic/eui'; +import { LoadingBadges } from '../badges/slo_badges'; +import { SloIndicatorTypeBadge } from '../badges/slo_indicator_type_badge'; +import { SloTimeWindowBadge } from '../badges/slo_time_window_badge'; +import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; +import { SloRulesBadge } from '../badges/slo_rules_badge'; +import { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; + +interface Props { + hasGroupBy: boolean; + activeAlerts?: number; + slo: SLOWithSummaryResponse; + rules: Array> | undefined; + handleCreateRule: () => void; +} + +const Container = styled.div<{ hasGroupBy: boolean }>` + position: absolute; + display: inline-block; + top: ${({ hasGroupBy }) => (hasGroupBy ? '55px' : '35px')}; + left: 7px; + z-index: 1; + border-radius: ${({ theme }) => theme.eui.euiBorderRadius}; +`; + +export function SloCardItemBadges({ + slo, + activeAlerts, + rules, + handleCreateRule, + hasGroupBy, +}: Props) { + return ( + + + {!slo.summary ? ( + + ) : ( + <> + + + + + + )} + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx new file mode 100644 index 0000000000000..3768bdbb7dac3 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGrid, EuiFlexItem, EuiPanel, EuiSkeletonText } from '@elastic/eui'; +import { SLOWithSummaryResponse, ALL_VALUE } from '@kbn/slo-schema'; +import { EuiFlexGridProps } from '@elastic/eui/src/components/flex/flex_grid'; +import { ActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; +import type { UseFetchRulesForSloResponse } from '../../../../hooks/slo/use_fetch_rules_for_slo'; +import { useFetchHistoricalSummary } from '../../../../hooks/slo/use_fetch_historical_summary'; +import { SloCardItem } from './slo_card_item'; + +export interface Props { + sloList: SLOWithSummaryResponse[]; + loading: boolean; + error: boolean; + cardsPerRow?: string; + activeAlertsBySlo: ActiveAlerts; + rulesBySlo?: UseFetchRulesForSloResponse['data']; +} + +export function SloListCardView({ + sloList, + loading, + error, + cardsPerRow, + rulesBySlo, + activeAlertsBySlo, +}: Props) { + const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } = + useFetchHistoricalSummary({ + list: sloList.map((slo) => ({ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE })), + }); + + if (loading && sloList.length === 0) { + return ; + } + + return ( + + {sloList.map((slo) => ( + + + historicalSummary.sloId === slo.id && + historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE) + )?.data + } + historicalSummaryLoading={historicalSummaryLoading} + cardsPerRow={Number(cardsPerRow)} + /> + + ))} + + ); +} + +function LoadingSloGrid({ gridSize }: { gridSize: number }) { + const ROWS = 4; + const COLUMNS = gridSize; + const loaders = Array(ROWS * COLUMNS).fill(null); + return ( + <> + + {loaders.map((_, i) => ( + + + + {' '} + + ))} + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/common/burn_rate_rule_flyout.tsx b/x-pack/plugins/observability/public/pages/slos/components/common/burn_rate_rule_flyout.tsx new file mode 100644 index 0000000000000..a02730231ae5f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/common/burn_rate_rule_flyout.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { useQueryClient } from '@tanstack/react-query'; +import { useGetFilteredRuleTypes } from '../../../../hooks/use_get_filtered_rule_types'; +import { sloKeys } from '../../../../hooks/slo/query_key_factory'; +import { useKibana } from '../../../../utils/kibana_react'; +import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../../../../common/constants'; +import { sloFeatureId } from '../../../../../common'; + +export function BurnRateRuleFlyout({ + slo, + isAddRuleFlyoutOpen, + setIsAddRuleFlyoutOpen, +}: { + slo: SLOWithSummaryResponse; + isAddRuleFlyoutOpen: boolean; + setIsAddRuleFlyoutOpen: (value: boolean) => void; +}) { + const { + triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, + } = useKibana().services; + + const filteredRuleTypes = useGetFilteredRuleTypes(); + + const queryClient = useQueryClient(); + + const handleSavedRule = async () => { + queryClient.invalidateQueries({ queryKey: sloKeys.rules(), exact: false }); + }; + + return isAddRuleFlyoutOpen ? ( + { + setIsAddRuleFlyoutOpen(false); + }} + useRuleProducer + /> + ) : null; +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_item_actions.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_item_actions.tsx new file mode 100644 index 0000000000000..4fb03968d40a0 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_item_actions.tsx @@ -0,0 +1,217 @@ +/* + * 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 { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + EuiButtonIconProps, + useEuiShadow, + EuiPanel, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import styled from 'styled-components'; +import { useCapabilities } from '../../../hooks/slo/use_capabilities'; +import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; +import { useKibana } from '../../../utils/kibana_react'; +import { paths } from '../../../../common/locators/paths'; +import { RulesParams } from '../../../locators/rules'; +import { rulesLocatorID } from '../../../../common'; +import { + transformCreateSLOFormToCreateSLOInput, + transformSloResponseToCreateSloForm, +} from '../../slo_edit/helpers/process_slo_form_values'; + +interface Props { + slo: SLOWithSummaryResponse; + isActionsPopoverOpen: boolean; + setIsActionsPopoverOpen: (value: boolean) => void; + setDeleteConfirmationModalOpen: (value: boolean) => void; + setIsAddRuleFlyoutOpen: (value: boolean) => void; + btnProps?: Partial; +} +const CustomShadowPanel = styled(EuiPanel)<{ shadow: string }>` + ${(props) => props.shadow} +`; + +function IconPanel({ children, hasPanel }: { children: JSX.Element; hasPanel: boolean }) { + const shadow = useEuiShadow('s'); + if (!hasPanel) return children; + return ( + + {children} + + ); +} + +export function SloItemActions({ + slo, + isActionsPopoverOpen, + setIsActionsPopoverOpen, + setIsAddRuleFlyoutOpen, + setDeleteConfirmationModalOpen, + btnProps, +}: Props) { + const { + application: { navigateToUrl }, + http: { basePath }, + share: { + url: { locators }, + }, + } = useKibana().services; + const { hasWriteCapabilities } = useCapabilities(); + const { mutate: cloneSlo } = useCloneSlo(); + + const sloDetailsUrl = basePath.prepend( + paths.observability.sloDetails( + slo.id, + slo.groupBy !== ALL_VALUE && slo.instanceId ? slo.instanceId : undefined + ) + ); + + const handleClickActions = () => { + setIsActionsPopoverOpen(!isActionsPopoverOpen); + }; + + const handleViewDetails = () => { + navigateToUrl(sloDetailsUrl); + }; + + const handleEdit = () => { + navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id))); + }; + + const handleNavigateToRules = async () => { + const locator = locators.get(rulesLocatorID); + locator?.navigate({ params: { sloId: slo.id } }, { replace: false }); + }; + + const handleClone = () => { + const newSlo = transformCreateSLOFormToCreateSLOInput( + transformSloResponseToCreateSloForm({ ...slo, name: `[Copy] ${slo.name}` })! + ); + + cloneSlo({ slo: newSlo, originalSloId: slo.id }); + setIsActionsPopoverOpen(false); + }; + + const handleDelete = () => { + setDeleteConfirmationModalOpen(true); + setIsActionsPopoverOpen(false); + }; + + const handleCreateRule = () => { + setIsActionsPopoverOpen(false); + setIsAddRuleFlyoutOpen(true); + }; + + const btn = ( + + ); + + return ( + {btn} : btn} + panelPaddingSize="m" + closePopover={handleClickActions} + isOpen={isActionsPopoverOpen} + > + + {i18n.translate('xpack.observability.slo.item.actions.details', { + defaultMessage: 'Details', + })} + , + + {i18n.translate('xpack.observability.slo.item.actions.edit', { + defaultMessage: 'Edit', + })} + , + + {i18n.translate('xpack.observability.slo.item.actions.createRule', { + defaultMessage: 'Create new alert rule', + })} + , + + {i18n.translate('xpack.observability.slo.item.actions.manageRules', { + defaultMessage: 'Manage rules', + })} + , + + {i18n.translate('xpack.observability.slo.item.actions.clone', { + defaultMessage: 'Clone', + })} + , + + {i18n.translate('xpack.observability.slo.item.actions.delete', { + defaultMessage: 'Delete', + })} + , + ]} + /> + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index 380d0100db1a1..ee1fff8e17c1d 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -8,9 +8,12 @@ import { EuiFlexGroup, EuiFlexItem, EuiPagination } from '@elastic/eui'; import { useIsMutating } from '@tanstack/react-query'; import React, { useState } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { SlosView } from './slos_view'; +import { SLO_CARD_VIEW_PER_ROW_SIZE } from './card_view/cards_per_row'; +import { SLOViewType, ToggleSLOView } from './toggle_slo_view'; import { useFetchSloList } from '../../../hooks/slo/use_fetch_slo_list'; import { useUrlSearchState } from '../hooks/use_url_search_state'; -import { SloListItems } from './slo_list_items'; import { SloListSearchBar, SortField } from './slo_list_search_bar'; export interface Props { @@ -24,6 +27,8 @@ export function SloList({ autoRefresh }: Props) { const [sort, setSort] = useState(state.sort.by); const [direction] = useState<'asc' | 'desc'>(state.sort.direction); + const [sloView, setSLOView] = useState('cardView'); + const { isLoading, isRefetching, @@ -43,6 +48,7 @@ export function SloList({ autoRefresh }: Props) { const isCloningSlo = Boolean(useIsMutating(['cloningSlo'])); const isUpdatingSlo = Boolean(useIsMutating(['updatingSlo'])); const isDeletingSlo = Boolean(useIsMutating(['deleteSlo'])); + const [cardsPerRow, setCardsPerRow] = useLocalStorage(SLO_CARD_VIEW_PER_ROW_SIZE, '4'); const handlePageClick = (pageNumber: number) => { setPage(pageNumber); @@ -71,9 +77,16 @@ export function SloList({ autoRefresh }: Props) { initialState={state} /> - - + + + {total > 0 ? ( diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index ae843977a0ee7..90757be5dc7a9 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -5,37 +5,16 @@ * 2.0. */ -import { - EuiButtonIcon, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiPopover, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { ALL_VALUE, HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import type { Rule } from '@kbn/triggers-actions-ui-plugin/public'; -import { useQueryClient } from '@tanstack/react-query'; import React, { useState } from 'react'; +import { useSloFormattedSummary } from '../hooks/use_slo_summary'; +import { BurnRateRuleFlyout } from './common/burn_rate_rule_flyout'; +import { useSloListActions } from '../hooks/use_slo_list_actions'; +import { SloItemActions } from './slo_item_actions'; import { SloDeleteConfirmationModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; -import { rulesLocatorID, sloFeatureId } from '../../../../common'; -import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../../../common/constants'; -import { paths } from '../../../../common/locators/paths'; -import { sloKeys } from '../../../hooks/slo/query_key_factory'; -import { useCapabilities } from '../../../hooks/slo/use_capabilities'; -import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; -import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; import type { SloRule } from '../../../hooks/slo/use_fetch_rules_for_slo'; -import { useGetFilteredRuleTypes } from '../../../hooks/use_get_filtered_rule_types'; -import type { RulesParams } from '../../../locators/rules'; -import { useKibana } from '../../../utils/kibana_react'; -import { - transformCreateSLOFormToCreateSLOInput, - transformSloResponseToCreateSloForm, -} from '../../slo_edit/helpers/process_slo_form_values'; import { SloBadges } from './badges/slo_badges'; import { SloSummary } from './slo_summary'; @@ -54,80 +33,18 @@ export function SloListItem({ historicalSummaryLoading, activeAlerts, }: SloListItemProps) { - const { - application: { navigateToUrl }, - http: { basePath }, - share: { - url: { locators }, - }, - triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, - } = useKibana().services; - const { hasWriteCapabilities } = useCapabilities(); - const queryClient = useQueryClient(); - - const filteredRuleTypes = useGetFilteredRuleTypes(); - - const { mutate: cloneSlo } = useCloneSlo(); - const { mutate: deleteSlo } = useDeleteSlo(); - const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false); const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); - const handleClickActions = () => { - setIsActionsPopoverOpen(!isActionsPopoverOpen); - }; - - const sloDetailsUrl = basePath.prepend( - paths.observability.sloDetails( - slo.id, - slo.groupBy !== ALL_VALUE && slo.instanceId ? slo.instanceId : undefined - ) - ); - const handleViewDetails = () => { - navigateToUrl(sloDetailsUrl); - }; - - const handleEdit = () => { - navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id))); - }; - - const handleCreateRule = () => { - setIsActionsPopoverOpen(false); - setIsAddRuleFlyoutOpen(true); - }; - - const handleSavedRule = async () => { - queryClient.invalidateQueries({ queryKey: sloKeys.rules(), exact: false }); - }; + const { sloDetailsUrl } = useSloFormattedSummary(slo); - const handleNavigateToRules = async () => { - const locator = locators.get(rulesLocatorID); - locator?.navigate({ params: { sloId: slo.id } }, { replace: false }); - }; - - const handleClone = () => { - const newSlo = transformCreateSLOFormToCreateSLOInput( - transformSloResponseToCreateSloForm({ ...slo, name: `[Copy] ${slo.name}` })! - ); - - cloneSlo({ slo: newSlo, originalSloId: slo.id }); - setIsActionsPopoverOpen(false); - }; - - const handleDelete = () => { - setDeleteConfirmationModalOpen(true); - setIsActionsPopoverOpen(false); - }; - - const handleDeleteConfirm = () => { - setDeleteConfirmationModalOpen(false); - deleteSlo({ id: slo.id, name: slo.name }); - }; - - const handleDeleteCancel = () => { - setDeleteConfirmationModalOpen(false); - }; + const { handleCreateRule, handleDeleteCancel, handleDeleteConfirm } = useSloListActions({ + slo, + setDeleteConfirmationModalOpen, + setIsActionsPopoverOpen, + setIsAddRuleFlyoutOpen, + }); return ( @@ -172,113 +89,21 @@ export function SloListItem({ {/* ACTIONS */} - - } - panelPaddingSize="m" - closePopover={handleClickActions} - isOpen={isActionsPopoverOpen} - > - - {i18n.translate('xpack.observability.slo.item.actions.details', { - defaultMessage: 'Details', - })} - , - - {i18n.translate('xpack.observability.slo.item.actions.edit', { - defaultMessage: 'Edit', - })} - , - - {i18n.translate('xpack.observability.slo.item.actions.createRule', { - defaultMessage: 'Create new alert rule', - })} - , - - {i18n.translate('xpack.observability.slo.item.actions.manageRules', { - defaultMessage: 'Manage rules', - })} - , - - {i18n.translate('xpack.observability.slo.item.actions.clone', { - defaultMessage: 'Clone', - })} - , - - {i18n.translate('xpack.observability.slo.item.actions.delete', { - defaultMessage: 'Delete', - })} - , - ]} - /> - + - {isAddRuleFlyoutOpen ? ( - { - setIsAddRuleFlyoutOpen(false); - }} - useRuleProducer - /> - ) : null} + {isDeleteConfirmationModalOpen ? ( = (props: Props) => [slo.id, slo.instanceId ?? ALL_VALUE] as [string, string] - ); - - const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIdsAndInstanceIds }); - const { data: rulesBySlo } = useFetchRulesForSlo({ - sloIds: sloIdsAndInstanceIds.map((item) => item[0]), - }); +export function SloListItems({ sloList, activeAlertsBySlo, rulesBySlo }: Props) { const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } = useFetchHistoricalSummary({ list: sloList.map((slo) => ({ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE })), }); - if (!loading && !error && sloList.length === 0) { - return ; - } - if (!loading && error) { - return ; - } - return ( {sloList.map((slo) => ( diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx index 401118e3c8dfc..77d23d6301aed 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx @@ -6,13 +6,11 @@ */ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; -import { useKibana } from '../../../utils/kibana_react'; +import { useSloFormattedSummary } from '../hooks/use_slo_summary'; import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; -import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { SloSparkline } from './slo_sparkline'; export interface Props { @@ -22,18 +20,12 @@ export interface Props { } export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoading }: Props) { - const { uiSettings } = useKibana().services; - const percentFormat = uiSettings.get('format:percent:defaultPattern'); + const { sliValue, sloTarget, errorBudgetRemaining } = useSloFormattedSummary(slo); const isSloFailed = slo.summary.status === 'VIOLATED' || slo.summary.status === 'DEGRADING'; const titleColor = isSloFailed ? 'danger' : ''; const errorBudgetBurnDownData = formatHistoricalData(historicalSummary, 'error_budget_remaining'); const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value'); - const errorBudgetRemaining = - slo.summary.errorBudget.remaining <= 0 - ? Math.trunc(slo.summary.errorBudget.remaining * 100) / 100 - : slo.summary.errorBudget.remaining; - return ( @@ -48,13 +40,9 @@ export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoadi [slo.id, slo.instanceId ?? ALL_VALUE] as [string, string] + ); + + const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIdsAndInstanceIds }); + const { data: rulesBySlo } = useFetchRulesForSlo({ + sloIds: sloIdsAndInstanceIds.map((item) => item[0]), + }); + + if (!loading && !error && sloList.length === 0) { + return ; + } + if (!loading && error) { + return ; + } + + return sloView === 'cardView' ? ( + + + + ) : ( + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx new file mode 100644 index 0000000000000..43b5d849b2e0b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonGroup, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiPopoverTitle, +} from '@elastic/eui'; +import { CardsPerRow } from './card_view/cards_per_row'; + +export type SLOViewType = 'cardView' | 'listView'; + +interface Props { + setCardsPerRow: (gridSize?: string) => void; + setSLOView: (view: SLOViewType) => void; + sloView: SLOViewType; +} +const toggleButtonsIcons = [ + { + id: `cardView`, + label: 'Card View', + iconType: 'visGauge', + 'data-test-subj': 'sloCardViewButton', + }, + { + id: `listView`, + label: 'List View', + iconType: 'list', + 'data-test-subj': 'sloListViewButton', + }, +]; + +export function ToggleSLOView({ sloView, setSLOView, setCardsPerRow }: Props) { + return ( + + + setSLOView(id as SLOViewType)} + isIconOnly + /> + + {sloView === 'cardView' && ( + + + + )} + + ); +} + +function ViewSettings({ setCardsPerRow }: { setCardsPerRow: (cardsPerRow?: string) => void }) { + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + + return ( + setIsPopoverOpen(!isPopoverOpen)} + /> + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + anchorPosition="downCenter" + > + + + +
+ +
+
+ ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/hooks/use_slo_list_actions.ts b/x-pack/plugins/observability/public/pages/slos/hooks/use_slo_list_actions.ts new file mode 100644 index 0000000000000..169e1e54c5222 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/hooks/use_slo_list_actions.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; + +export function useSloListActions({ + slo, + setIsAddRuleFlyoutOpen, + setIsActionsPopoverOpen, + setDeleteConfirmationModalOpen, +}: { + slo: SLOWithSummaryResponse; + setIsActionsPopoverOpen: (val: boolean) => void; + setIsAddRuleFlyoutOpen: (val: boolean) => void; + setDeleteConfirmationModalOpen: (val: boolean) => void; +}) { + const { mutate: deleteSlo } = useDeleteSlo(); + + const handleDeleteConfirm = () => { + setDeleteConfirmationModalOpen(false); + deleteSlo({ id: slo.id, name: slo.name }); + }; + + const handleDeleteCancel = () => { + setDeleteConfirmationModalOpen(false); + }; + const handleCreateRule = () => { + setIsActionsPopoverOpen(false); + setIsAddRuleFlyoutOpen(true); + }; + + return { + handleDeleteConfirm, + handleDeleteCancel, + handleCreateRule, + }; +} diff --git a/x-pack/plugins/observability/public/pages/slos/hooks/use_slo_summary.ts b/x-pack/plugins/observability/public/pages/slos/hooks/use_slo_summary.ts new file mode 100644 index 0000000000000..547bd41b4f5db --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/hooks/use_slo_summary.ts @@ -0,0 +1,50 @@ +/* + * 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 numeral from '@elastic/numeral'; +import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { paths } from '../../../../common/locators/paths'; +import { useKibana } from '../../../utils/kibana_react'; +import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; + +export const useSloFormattedSummary = (slo: SLOWithSummaryResponse) => { + const { + http: { basePath }, + } = useKibana().services; + const { uiSettings } = useKibana().services; + const percentFormat = uiSettings.get('format:percent:defaultPattern'); + + const sliValue = + slo.summary.status === 'NO_DATA' + ? NOT_AVAILABLE_LABEL + : numeral(slo.summary.sliValue).format(percentFormat); + + const sloTarget = numeral(slo.objective.target).format(percentFormat); + const errorBudgetRemaining = + slo.summary.errorBudget.remaining <= 0 + ? Math.trunc(slo.summary.errorBudget.remaining * 100) / 100 + : slo.summary.errorBudget.remaining; + + const errorBudgetRemainingTitle = + slo.summary.status === 'NO_DATA' + ? NOT_AVAILABLE_LABEL + : numeral(errorBudgetRemaining).format(percentFormat); + + const sloDetailsUrl = basePath.prepend( + paths.observability.sloDetails( + slo.id, + slo.groupBy !== ALL_VALUE && slo.instanceId ? slo.instanceId : undefined + ) + ); + + return { + sloDetailsUrl, + sliValue, + sloTarget, + errorBudgetRemaining: errorBudgetRemainingTitle, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts b/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts index 7a0c03215fb91..aaa87cb921ae6 100644 --- a/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts +++ b/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts @@ -8,6 +8,7 @@ import { useHistory } from 'react-router-dom'; import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import deepmerge from 'deepmerge'; +import { ViewMode } from '../components/badges/slo_badges'; import type { SortField } from '../components/slo_list_search_bar'; export const SLO_LIST_SEARCH_URL_STORAGE_KEY = 'search'; @@ -19,12 +20,14 @@ export interface SearchState { by: SortField; direction: 'asc' | 'desc'; }; + viewMode: ViewMode; } export const DEFAULT_STATE = { kqlQuery: '', page: 0, sort: { by: 'status' as const, direction: 'desc' as const }, + viewMode: 'compact' as const, }; export function useUrlSearchState(): { diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index 3e19b7a466be5..6c63d270ef8e8 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, screen, waitFor } from '@testing-library/react'; +import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -211,6 +211,9 @@ describe('SLOs Page', () => { await act(async () => { render(); }); + expect(await screen.findByTestId('sloListViewButton')).toBeTruthy(); + + fireEvent.click(screen.getByTestId('sloListViewButton')); expect(screen.queryByTestId('slosPage')).toBeTruthy(); expect(screen.queryByTestId('sloList')).toBeTruthy(); @@ -229,6 +232,8 @@ describe('SLOs Page', () => { await act(async () => { render(); }); + expect(await screen.findByTestId('sloListViewButton')).toBeTruthy(); + fireEvent.click(screen.getByTestId('sloListViewButton')); screen.getAllByLabelText('Actions').at(0)?.click(); @@ -256,7 +261,8 @@ describe('SLOs Page', () => { await act(async () => { render(); }); - + expect(await screen.findByTestId('sloListViewButton')).toBeTruthy(); + fireEvent.click(screen.getByTestId('sloListViewButton')); screen.getAllByLabelText('Actions').at(0)?.click(); await waitForEuiPopoverOpen(); @@ -281,7 +287,8 @@ describe('SLOs Page', () => { await act(async () => { render(); }); - + expect(await screen.findByTestId('sloListViewButton')).toBeTruthy(); + fireEvent.click(screen.getByTestId('sloListViewButton')); screen.getAllByLabelText('Actions').at(0)?.click(); await waitForEuiPopoverOpen(); @@ -307,6 +314,8 @@ describe('SLOs Page', () => { render(); }); + expect(await screen.findByTestId('sloListViewButton')).toBeTruthy(); + fireEvent.click(screen.getByTestId('sloListViewButton')); screen.getAllByLabelText('Actions').at(0)?.click(); await waitForEuiPopoverOpen(); @@ -337,6 +346,8 @@ describe('SLOs Page', () => { render(); }); + expect(await screen.findByTestId('sloListViewButton')).toBeTruthy(); + fireEvent.click(screen.getByTestId('sloListViewButton')); screen.getAllByLabelText('Actions').at(0)?.click(); await waitForEuiPopoverOpen(); diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx index 3b778013a42d1..d8cfc46ec5a22 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx @@ -66,6 +66,8 @@ export const getComments = ({ regenerateMessage(currentConversation.id); }; + const connectorTypeTitle = currentConversation.apiConfig.connectorTypeTitle ?? ''; + const extraLoadingComment = isFetchingResponse ? [ { @@ -75,6 +77,7 @@ export const getComments = ({ children: ( ; regenerateMessage: () => void; transformMessage: (message: string) => ContentMessage; @@ -29,6 +30,7 @@ interface Props { export const StreamComment = ({ amendMessage, content, + connectorTypeTitle, index, isError = false, isFetching = false, @@ -40,6 +42,7 @@ export const StreamComment = ({ const { error, isLoading, isStreaming, pendingMessage, setComplete } = useStream({ amendMessage, content, + connectorTypeTitle, reader, isError, }); diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts index 764db1b3990ae..54a5684d20442 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts @@ -9,6 +9,8 @@ import { API_ERROR } from '../translations'; import type { PromptObservableState } from './types'; import { Subject } from 'rxjs'; +import { EventStreamCodec } from '@smithy/eventstream-codec'; +import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; describe('getStreamObservable', () => { const mockReader = { read: jest.fn(), @@ -22,29 +24,102 @@ describe('getStreamObservable', () => { beforeEach(() => { jest.clearAllMocks(); }); + it('should emit loading state and chunks for Bedrock', (done) => { + const completeSubject = new Subject(); + const expectedStates: PromptObservableState[] = [ + { chunks: [], loading: true }, + { + // when i log the actual emit, chunks equal to message.split(''); test is wrong + chunks: ['My', ' new', ' message'], + message: 'My', + loading: true, + }, + { + chunks: ['My', ' new', ' message'], + message: 'My new', + loading: true, + }, + { + chunks: ['My', ' new', ' message'], + message: 'My new message', + loading: true, + }, + { + chunks: ['My', ' new', ' message'], + message: 'My new message', + loading: false, + }, + ]; - it('should emit loading state and chunks', (done) => { + mockReader.read + .mockResolvedValueOnce({ + done: false, + value: encodeBedrockResponse('My'), + }) + .mockResolvedValueOnce({ + done: false, + value: encodeBedrockResponse(' new'), + }) + .mockResolvedValueOnce({ + done: false, + value: encodeBedrockResponse(' message'), + }) + .mockResolvedValue({ + done: true, + }); + + const source = getStreamObservable({ + connectorTypeTitle: 'Amazon Bedrock', + isError: false, + reader: typedReader, + setLoading, + }); + const emittedStates: PromptObservableState[] = []; + + source.subscribe({ + next: (state) => { + return emittedStates.push(state); + }, + complete: () => { + expect(emittedStates).toEqual(expectedStates); + done(); + + completeSubject.subscribe({ + next: () => { + expect(setLoading).toHaveBeenCalledWith(false); + expect(typedReader.cancel).toHaveBeenCalled(); + done(); + }, + }); + }, + error: (err) => done(err), + }); + }); + it('should emit loading state and chunks for OpenAI', (done) => { + const chunk1 = `data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"My"}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" new"}}]}`; + const chunk2 = `\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" message"}}]}\ndata: [DONE]`; const completeSubject = new Subject(); const expectedStates: PromptObservableState[] = [ { chunks: [], loading: true }, { - chunks: ['one chunk ', 'another chunk', ''], - message: 'one chunk ', + // when i log the actual emit, chunks equal to message.split(''); test is wrong + chunks: ['My', ' new', ' message'], + message: 'My', loading: true, }, { - chunks: ['one chunk ', 'another chunk', ''], - message: 'one chunk another chunk', + chunks: ['My', ' new', ' message'], + message: 'My new', loading: true, }, { - chunks: ['one chunk ', 'another chunk', ''], - message: 'one chunk another chunk', + chunks: ['My', ' new', ' message'], + message: 'My new message', loading: true, }, { - chunks: ['one chunk ', 'another chunk', ''], - message: 'one chunk another chunk', + chunks: ['My', ' new', ' message'], + message: 'My new message', loading: false, }, ]; @@ -52,11 +127,11 @@ describe('getStreamObservable', () => { mockReader.read .mockResolvedValueOnce({ done: false, - value: new Uint8Array(new TextEncoder().encode(`one chunk `)), + value: new Uint8Array(new TextEncoder().encode(chunk1)), }) .mockResolvedValueOnce({ done: false, - value: new Uint8Array(new TextEncoder().encode(`another chunk`)), + value: new Uint8Array(new TextEncoder().encode(chunk2)), }) .mockResolvedValueOnce({ done: false, @@ -66,11 +141,91 @@ describe('getStreamObservable', () => { done: true, }); - const source = getStreamObservable(typedReader, setLoading, false); + const source = getStreamObservable({ + connectorTypeTitle: 'OpenAI', + isError: false, + reader: typedReader, + setLoading, + }); const emittedStates: PromptObservableState[] = []; source.subscribe({ - next: (state) => emittedStates.push(state), + next: (state) => { + return emittedStates.push(state); + }, + complete: () => { + expect(emittedStates).toEqual(expectedStates); + done(); + + completeSubject.subscribe({ + next: () => { + expect(setLoading).toHaveBeenCalledWith(false); + expect(typedReader.cancel).toHaveBeenCalled(); + done(); + }, + }); + }, + error: (err) => done(err), + }); + }); + it('should emit loading state and chunks for partial response OpenAI', (done) => { + const chunk1 = `data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"My"}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" new"`; + const chunk2 = `}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" message"}}]}\ndata: [DONE]`; + const completeSubject = new Subject(); + const expectedStates: PromptObservableState[] = [ + { chunks: [], loading: true }, + { + // when i log the actual emit, chunks equal to message.split(''); test is wrong + chunks: ['My', ' new', ' message'], + message: 'My', + loading: true, + }, + { + chunks: ['My', ' new', ' message'], + message: 'My new', + loading: true, + }, + { + chunks: ['My', ' new', ' message'], + message: 'My new message', + loading: true, + }, + { + chunks: ['My', ' new', ' message'], + message: 'My new message', + loading: false, + }, + ]; + + mockReader.read + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode(chunk1)), + }) + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode(chunk2)), + }) + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode('')), + }) + .mockResolvedValue({ + done: true, + }); + + const source = getStreamObservable({ + connectorTypeTitle: 'OpenAI', + isError: false, + reader: typedReader, + setLoading, + }); + const emittedStates: PromptObservableState[] = []; + + source.subscribe({ + next: (state) => { + return emittedStates.push(state); + }, complete: () => { expect(emittedStates).toEqual(expectedStates); done(); @@ -112,7 +267,12 @@ describe('getStreamObservable', () => { done: true, }); - const source = getStreamObservable(typedReader, setLoading, true); + const source = getStreamObservable({ + connectorTypeTitle: 'OpenAI', + isError: true, + reader: typedReader, + setLoading, + }); const emittedStates: PromptObservableState[] = []; source.subscribe({ @@ -138,7 +298,12 @@ describe('getStreamObservable', () => { const error = new Error('Test Error'); // Simulate an error mockReader.read.mockRejectedValue(error); - const source = getStreamObservable(typedReader, setLoading, false); + const source = getStreamObservable({ + connectorTypeTitle: 'OpenAI', + isError: false, + reader: typedReader, + setLoading, + }); source.subscribe({ next: (state) => {}, @@ -157,3 +322,16 @@ describe('getStreamObservable', () => { }); }); }); + +function encodeBedrockResponse(completion: string) { + return new EventStreamCodec(toUtf8, fromUtf8).encode({ + headers: {}, + body: Uint8Array.from( + Buffer.from( + JSON.stringify({ + bytes: Buffer.from(JSON.stringify({ completion })).toString('base64'), + }) + ) + ), + }); +} diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts index b30be69b82cae..ce7a38811f229 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts @@ -7,10 +7,18 @@ import { concatMap, delay, finalize, Observable, of, scan, timestamp } from 'rxjs'; import type { Dispatch, SetStateAction } from 'react'; -import { API_ERROR } from '../translations'; +import { EventStreamCodec } from '@smithy/eventstream-codec'; +import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; import type { PromptObservableState } from './types'; +import { API_ERROR } from '../translations'; const MIN_DELAY = 35; +interface StreamObservable { + connectorTypeTitle: string; + reader: ReadableStreamDefaultReader; + setLoading: Dispatch>; + isError: boolean; +} /** * Returns an Observable that reads data from a ReadableStream and emits values representing the state of the data processing. * @@ -19,52 +27,155 @@ const MIN_DELAY = 35; * @param isError - indicates whether the reader response is an error message or not * @returns {Observable} An Observable that emits PromptObservableState */ -export const getStreamObservable = ( - reader: ReadableStreamDefaultReader, - setLoading: Dispatch>, - isError: boolean -): Observable => +export const getStreamObservable = ({ + connectorTypeTitle, + isError, + reader, + setLoading, +}: StreamObservable): Observable => new Observable((observer) => { observer.next({ chunks: [], loading: true }); const decoder = new TextDecoder(); const chunks: string[] = []; - function read() { + // Initialize an empty string to store the OpenAI buffer. + let openAIBuffer: string = ''; + + // Initialize an empty Uint8Array to store the Bedrock concatenated buffer. + let bedrockBuffer: Uint8Array = new Uint8Array(0); + function readOpenAI() { reader .read() .then(({ done, value }: { done: boolean; value?: Uint8Array }) => { try { if (done) { + if (openAIBuffer) { + chunks.push(getOpenAIChunks([openAIBuffer])[0]); + } observer.next({ chunks, - message: getMessageFromChunks(chunks), + message: chunks.join(''), loading: false, }); observer.complete(); return; } + const decoded = decoder.decode(value); - const content = isError - ? // we format errors as {message: string; status_code: number} - `${API_ERROR}\n\n${JSON.parse(decoded).message}` - : // all other responses are just strings (handled by subaction invokeStream) - decoded; - chunks.push(content); - observer.next({ - chunks, - message: getMessageFromChunks(chunks), - loading: true, + let nextChunks; + if (isError) { + nextChunks = [`${API_ERROR}\n\n${JSON.parse(decoded).message}`]; + } else { + const lines = decoded.split('\n'); + lines[0] = openAIBuffer + lines[0]; + openAIBuffer = lines.pop() || ''; + nextChunks = getOpenAIChunks(lines); + } + nextChunks.forEach((chunk: string) => { + chunks.push(chunk); + observer.next({ + chunks, + message: chunks.join(''), + loading: true, + }); }); } catch (err) { observer.error(err); return; } - read(); + readOpenAI(); + }) + .catch((err) => { + observer.error(err); + }); + } + function readBedrock() { + reader + .read() + .then(({ done, value }: { done: boolean; value?: Uint8Array }) => { + try { + if (done) { + observer.next({ + chunks, + message: chunks.join(''), + loading: false, + }); + observer.complete(); + return; + } + + let content; + if (isError) { + content = `${API_ERROR}\n\n${JSON.parse(decoder.decode(value)).message}`; + chunks.push(content); + observer.next({ + chunks, + message: chunks.join(''), + loading: true, + }); + } else if (value != null) { + const chunk: Uint8Array = value; + + // Concatenate the current chunk to the existing buffer. + bedrockBuffer = concatChunks(bedrockBuffer, chunk); + // Get the length of the next message in the buffer. + let messageLength = getMessageLength(bedrockBuffer); + + // Initialize an array to store fully formed message chunks. + const buildChunks = []; + // Process the buffer until no complete messages are left. + while (bedrockBuffer.byteLength > 0 && bedrockBuffer.byteLength >= messageLength) { + // Extract a chunk of the specified length from the buffer. + const extractedChunk = bedrockBuffer.slice(0, messageLength); + // Add the extracted chunk to the array of fully formed message chunks. + buildChunks.push(extractedChunk); + // Remove the processed chunk from the buffer. + bedrockBuffer = bedrockBuffer.slice(messageLength); + // Get the length of the next message in the updated buffer. + messageLength = getMessageLength(bedrockBuffer); + } + + const awsDecoder = new EventStreamCodec(toUtf8, fromUtf8); + // Decode and parse each message chunk, extracting the 'completion' property. + buildChunks.forEach((bChunk) => { + const event = awsDecoder.decode(bChunk); + const body = JSON.parse( + Buffer.from(JSON.parse(decoder.decode(event.body)).bytes, 'base64').toString() + ); + content = body.completion; + chunks.push(content); + observer.next({ + chunks, + message: chunks.join(''), + loading: true, + }); + }); + } + } catch (err) { + observer.error(err); + return; + } + readBedrock(); }) .catch((err) => { observer.error(err); }); } - read(); + // this should never actually happen + function badConnector() { + observer.next({ + chunks: [ + `Invalid connector type - ${connectorTypeTitle} is not a supported GenAI connector.`, + ], + message: `Invalid connector type - ${connectorTypeTitle} is not a supported GenAI connector.`, + loading: false, + }); + observer.complete(); + } + + if (connectorTypeTitle === 'Amazon Bedrock') readBedrock(); + else if (connectorTypeTitle === 'OpenAI') readOpenAI(); + else badConnector(); + return () => { reader.cancel(); }; @@ -99,8 +210,55 @@ export const getStreamObservable = ( finalize(() => setLoading(false)) ); -function getMessageFromChunks(chunks: string[]) { - return chunks.join(''); +/** + * Parses an OpenAI response from a string. + * @param lines + * @returns {string[]} - Parsed string array from the OpenAI response. + */ +const getOpenAIChunks = (lines: string[]): string[] => { + const nextChunk = lines + .map((str) => str.substring(6)) + .filter((str) => !!str && str !== '[DONE]') + .map((line) => { + try { + const openaiResponse = JSON.parse(line); + return openaiResponse.choices[0]?.delta.content ?? ''; + } catch (err) { + return ''; + } + }); + return nextChunk; +}; + +/** + * Concatenates two Uint8Array buffers. + * + * @param {Uint8Array} a - First buffer. + * @param {Uint8Array} b - Second buffer. + * @returns {Uint8Array} - Concatenated buffer. + */ +function concatChunks(a: Uint8Array, b: Uint8Array): Uint8Array { + const newBuffer = new Uint8Array(a.length + b.length); + // Copy the contents of the first buffer to the new buffer. + newBuffer.set(a); + // Copy the contents of the second buffer to the new buffer starting from the end of the first buffer. + newBuffer.set(b, a.length); + return newBuffer; +} + +/** + * Gets the length of the next message from the buffer. + * + * @param {Uint8Array} buffer - Buffer containing the message. + * @returns {number} - Length of the next message. + */ +function getMessageLength(buffer: Uint8Array): number { + // If the buffer is empty, return 0. + if (buffer.byteLength === 0) return 0; + // Create a DataView to read the Uint32 value at the beginning of the buffer. + const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + // Read and return the Uint32 value (message length). + return view.getUint32(0, false); } export const getPlaceholderObservable = () => new Observable(); diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.test.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.test.tsx index efbc61999f2cc..c4f99884aa045 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.test.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.test.tsx @@ -11,20 +11,22 @@ import { useStream } from './use_stream'; const amendMessage = jest.fn(); const reader = jest.fn(); const cancel = jest.fn(); +const chunk1 = `data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"My"}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" new"}}]}`; +const chunk2 = `\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" message"}}]}\ndata: [DONE]`; const readerComplete = { read: reader .mockResolvedValueOnce({ done: false, - value: new Uint8Array(new TextEncoder().encode('one chunk ')), + value: new Uint8Array(new TextEncoder().encode(chunk1)), }) .mockResolvedValueOnce({ done: false, - value: new Uint8Array(new TextEncoder().encode(`another chunk`)), + value: new Uint8Array(new TextEncoder().encode(chunk2)), }) .mockResolvedValueOnce({ done: false, - value: new Uint8Array(new TextEncoder().encode(``)), + value: new Uint8Array(new TextEncoder().encode('')), }) .mockResolvedValue({ done: true, @@ -34,7 +36,12 @@ const readerComplete = { closed: jest.fn().mockResolvedValue(true), } as unknown as ReadableStreamDefaultReader; -const defaultProps = { amendMessage, reader: readerComplete, isError: false }; +const defaultProps = { + amendMessage, + reader: readerComplete, + isError: false, + connectorTypeTitle: 'OpenAI', +}; describe('useStream', () => { beforeEach(() => { jest.clearAllMocks(); @@ -57,7 +64,7 @@ describe('useStream', () => { error: undefined, isLoading: true, isStreaming: true, - pendingMessage: 'one chunk ', + pendingMessage: 'My', setComplete: expect.any(Function), }); }); @@ -67,7 +74,7 @@ describe('useStream', () => { error: undefined, isLoading: false, isStreaming: false, - pendingMessage: 'one chunk another chunk', + pendingMessage: 'My new message', setComplete: expect.any(Function), }); }); diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.tsx index 7de06589f87c7..9271758a8558e 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/use_stream.tsx @@ -7,13 +7,13 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import type { Subscription } from 'rxjs'; -import { share } from 'rxjs'; import { getPlaceholderObservable, getStreamObservable } from './stream_observable'; interface UseStreamProps { amendMessage: (message: string) => void; isError: boolean; content?: string; + connectorTypeTitle: string; reader?: ReadableStreamDefaultReader; } interface UseStream { @@ -39,6 +39,7 @@ interface UseStream { export const useStream = ({ amendMessage, content, + connectorTypeTitle, reader, isError, }: UseStreamProps): UseStream => { @@ -49,9 +50,9 @@ export const useStream = ({ const observer$ = useMemo( () => content == null && reader != null - ? getStreamObservable(reader, setLoading, isError) + ? getStreamObservable({ connectorTypeTitle, reader, setLoading, isError }) : getPlaceholderObservable(), - [content, isError, reader] + [content, isError, reader, connectorTypeTitle] ); const onCompleteStream = useCallback(() => { subscription?.unsubscribe(); @@ -66,7 +67,7 @@ export const useStream = ({ } }, [complete, onCompleteStream]); useEffect(() => { - const newSubscription = observer$.pipe(share()).subscribe({ + const newSubscription = observer$.subscribe({ next: ({ message, loading: isLoading }) => { setLoading(isLoading); setPendingMessage(message); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts index 708e8cd4e0364..0eeb309dd2257 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts @@ -5,13 +5,10 @@ * 2.0. */ import aws from 'aws4'; -import { Transform } from 'stream'; +import { PassThrough, Transform } from 'stream'; import { BedrockConnector } from './bedrock'; -import { waitFor } from '@testing-library/react'; import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; -import { EventStreamCodec } from '@smithy/eventstream-codec'; -import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { RunActionResponseSchema, StreamingResponseSchema } from '../../../common/bedrock/schema'; import { @@ -105,7 +102,7 @@ describe('BedrockConnector', () => { let stream; beforeEach(() => { stream = createStreamMock(); - stream.write(encodeBedrockResponse(mockResponseString)); + stream.write(new Uint8Array([1, 2, 3])); mockRequest = jest.fn().mockResolvedValue({ ...mockResponse, data: stream.transform }); // @ts-ignore connector.request = mockRequest; @@ -199,16 +196,9 @@ describe('BedrockConnector', () => { }); }); - it('transforms the response into a string', async () => { + it('responds with a readable stream', async () => { const response = await connector.invokeStream(aiAssistantBody); - - let responseBody: string = ''; - response.on('data', (data: string) => { - responseBody += data.toString(); - }); - await waitFor(() => { - expect(responseBody).toEqual(mockResponseString); - }); + expect(response instanceof PassThrough).toEqual(true); }); it('errors during API calls are properly handled', async () => { @@ -364,16 +354,3 @@ function createStreamMock() { }, }; } - -function encodeBedrockResponse(completion: string) { - return new EventStreamCodec(toUtf8, fromUtf8).encode({ - headers: {}, - body: Uint8Array.from( - Buffer.from( - JSON.stringify({ - bytes: Buffer.from(JSON.stringify({ completion })).toString('base64'), - }) - ) - ), - }); -} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts index 70f8e121e1519..ade589e54dc14 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts @@ -9,9 +9,7 @@ import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server'; import aws from 'aws4'; import type { AxiosError } from 'axios'; import { IncomingMessage } from 'http'; -import { PassThrough, Transform } from 'stream'; -import { EventStreamCodec } from '@smithy/eventstream-codec'; -import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; +import { PassThrough } from 'stream'; import { RunActionParamsSchema, RunActionResponseSchema, @@ -178,12 +176,12 @@ export class BedrockConnector extends SubActionConnector { * @param messages An array of messages to be sent to the API * @param model Optional model to be used for the API request. If not provided, the default model from the connector will be used. */ - public async invokeStream({ messages, model }: InvokeAIActionParams): Promise { + public async invokeStream({ messages, model }: InvokeAIActionParams): Promise { const res = (await this.streamApi({ body: JSON.stringify(formatBedrockBody({ messages })), model, })) as unknown as IncomingMessage; - return res.pipe(transformToString()); + return res; } /** @@ -222,25 +220,3 @@ const formatBedrockBody = ({ stop_sequences: ['\n\nHuman:'], }; }; - -/** - * Takes in a readable stream of data and returns a Transform stream that - * uses the AWS proprietary codec to parse the proprietary bedrock response into - * a string of the response text alone, returning the response string to the stream - */ -const transformToString = () => - new Transform({ - transform(chunk, encoding, callback) { - const encoder = new TextEncoder(); - const decoder = new EventStreamCodec(toUtf8, fromUtf8); - const event = decoder.decode(chunk); - const body = JSON.parse( - Buffer.from( - JSON.parse(new TextDecoder('utf-8').decode(event.body)).bytes, - 'base64' - ).toString() - ); - const newChunk = encoder.encode(body.completion); - callback(null, newChunk); - }, - }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts index 7769dd8592faf..c7d6feb6887ad 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts @@ -17,8 +17,7 @@ import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { RunActionResponseSchema, StreamingResponseSchema } from '../../../common/openai/schema'; import { initDashboard } from './create_dashboard'; -import { Transform } from 'stream'; -import { waitFor } from '@testing-library/react'; +import { PassThrough, Transform } from 'stream'; jest.mock('./create_dashboard'); describe('OpenAIConnector', () => { @@ -315,53 +314,11 @@ describe('OpenAIConnector', () => { await expect(connector.invokeStream(sampleOpenAiBody)).rejects.toThrow('API Error'); }); - it('transforms the response into a string', async () => { + it('responds with a readable stream', async () => { // @ts-ignore connector.request = mockStream(); const response = await connector.invokeStream(sampleOpenAiBody); - - let responseBody: string = ''; - response.on('data', (data: string) => { - responseBody += data.toString(); - }); - await waitFor(() => { - expect(responseBody).toEqual('My new'); - }); - }); - it('correctly buffers stream of json lines', async () => { - const chunk1 = `data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"My"}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" new"}}]}`; - const chunk2 = `\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" message"}}]}\ndata: [DONE]`; - - // @ts-ignore - connector.request = mockStream([chunk1, chunk2]); - - const response = await connector.invokeStream(sampleOpenAiBody); - - let responseBody: string = ''; - response.on('data', (data: string) => { - responseBody += data.toString(); - }); - await waitFor(() => { - expect(responseBody).toEqual('My new message'); - }); - }); - it('correctly buffers partial lines', async () => { - const chunk1 = `data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"My"}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" new"`; - - const chunk2 = `}}]}\ndata: {"object":"chat.completion.chunk","choices":[{"delta":{"content":" message"}}]}\ndata: [DONE]`; - - // @ts-ignore - connector.request = mockStream([chunk1, chunk2]); - - const response = await connector.invokeStream(sampleOpenAiBody); - - let responseBody: string = ''; - response.on('data', (data: string) => { - responseBody += data.toString(); - }); - await waitFor(() => { - expect(responseBody).toEqual('My new message'); - }); + expect(response instanceof PassThrough).toEqual(true); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.ts index 78fca4bd84198..8dfeac0be8502 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.ts @@ -7,8 +7,8 @@ import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server'; import type { AxiosError } from 'axios'; -import { PassThrough, Transform } from 'stream'; import { IncomingMessage } from 'http'; +import { PassThrough } from 'stream'; import { RunActionParamsSchema, RunActionResponseSchema, @@ -198,13 +198,13 @@ export class OpenAIConnector extends SubActionConnector { * the response from the streamApi method and returns the response string alone. * @param body - the OpenAI Invoke request body */ - public async invokeStream(body: InvokeAIActionParams): Promise { + public async invokeStream(body: InvokeAIActionParams): Promise { const res = (await this.streamApi({ body: JSON.stringify(body), stream: true, })) as unknown as IncomingMessage; - return res.pipe(new PassThrough()).pipe(transformToString()); + return res.pipe(new PassThrough()); } /** @@ -229,44 +229,3 @@ export class OpenAIConnector extends SubActionConnector { }; } } - -/** - * Takes in a readable stream of data and returns a Transform stream that - * parses the proprietary OpenAI response into a string of the response text alone, - * returning the response string to the stream - */ -const transformToString = () => { - let lineBuffer: string = ''; - const decoder = new TextDecoder(); - - return new Transform({ - transform(chunk, encoding, callback) { - const chunks = decoder.decode(chunk); - const lines = chunks.split('\n'); - lines[0] = lineBuffer + lines[0]; - lineBuffer = lines.pop() || ''; - callback(null, getNextChunk(lines)); - }, - flush(callback) { - // Emit an additional chunk with the content of lineBuffer if it has length - if (lineBuffer.length > 0) { - callback(null, getNextChunk([lineBuffer])); - } else { - callback(); - } - }, - }); -}; - -const getNextChunk = (lines: string[]) => { - const encoder = new TextEncoder(); - const nextChunk = lines - .map((str) => str.substring(6)) - .filter((str) => !!str && str !== '[DONE]') - .map((line) => { - const openaiResponse = JSON.parse(line); - return openaiResponse.choices[0]?.delta.content ?? ''; - }) - .join(''); - return encoder.encode(nextChunk); -}; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 401e9b6d8e94e..9c0f8b699540a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18621,9 +18621,7 @@ "xpack.grokDebugger.unknownErrorTitle": "Un problème est survenu", "xpack.idxMgmt.badgeAriaLabel": "{label}. Sélectionnez pour filtrer selon cet élément.", "xpack.idxMgmt.clearCacheIndicesAction.indexCacheClearedMessage": "Le cache de l'index {indexNames} a été effacé.", - "xpack.idxMgmt.clearCacheIndicesAction.successMessage": "Le cache a bien été effacé : [{indexNames}]", "xpack.idxMgmt.closeIndicesAction.indexClosedMessage": "L'index {indexNames} a été fermé.", - "xpack.idxMgmt.closeIndicesAction.successfullyClosedIndicesMessage": "Fermeture réussie : [{indexNames}]", "xpack.idxMgmt.componentTemplateDetails.summaryTab.notInUseDescription": "{createLink} un modèle d'index ou {editLink}-en un existant.", "xpack.idxMgmt.componentTemplateForm.stepLogistics.metaDescription": "Informations arbitraires sur le modèle stockées dans l'état du cluster. {learnMoreLink}", "xpack.idxMgmt.componentTemplateForm.stepLogistics.metaHelpText": "Utiliser le format JSON : {code}", @@ -18642,7 +18640,6 @@ "xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText": "Erreur lors de la suppression de {count} flux de données", "xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText": "{numSuccesses, plural, one {# flux de données} many {# flux de données} other {# flux de données}} supprimé", "xpack.idxMgmt.deleteIndicesAction.indexDeletedMessage": "L'index {indexNames} a été supprimé.", - "xpack.idxMgmt.deleteIndicesAction.successfullyDeletedIndicesMessage": "Suppression réussie : [{indexNames}]", "xpack.idxMgmt.deleteTemplatesModal.confirmButtonLabel": "Supprimer {numTemplatesToDelete, plural, one {modèle} many {modèles} other {modèles}}", "xpack.idxMgmt.deleteTemplatesModal.deleteDescription": "Vous êtes sur le point de supprimer {numTemplatesToDelete, plural, one {ce modèle} many {ces modèles} other {ces modèles}} :", "xpack.idxMgmt.deleteTemplatesModal.modalTitleText": "Supprimer {numTemplatesToDelete, plural, one {modèle} many {# modèles} other {# modèles}}", @@ -18660,9 +18657,7 @@ "xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryHelpText": "Valeur par défaut : requête {code}.", "xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeTypePopOver": "{type} correspond à un nombre, une date ou une plage d'adresses IP.", "xpack.idxMgmt.flushIndicesAction.indexFlushedMessage": "L'index {indexNames} a été vidé.", - "xpack.idxMgmt.flushIndicesAction.successfullyFlushedIndicesMessage": "Vidage effectué avec succès : [{indexNames}]", "xpack.idxMgmt.forceMergeIndicesAction.indexForcemergedMessage": "L'index {indexNames} a fait l'objet d'une fusion forcée.", - "xpack.idxMgmt.forceMergeIndicesAction.successfullyForceMergedIndicesMessage": "Fusion forcée effectué avec succès : [{indexNames}]", "xpack.idxMgmt.formWizard.stepAliases.aliasesEditorHelpText": "Utiliser le format JSON : {code}", "xpack.idxMgmt.formWizard.stepSettings.settingsEditorHelpText": "Utiliser le format JSON : {code}", "xpack.idxMgmt.goToDiscover.showIndexToolTip": "Afficher {indexName} dans Discover", @@ -18759,9 +18754,7 @@ "xpack.idxMgmt.mappingsEditor.sourceFieldDescription": "Le champ _source contient le corps du document JSON d'origine qui a été fourni au moment de l'indexation. Vous pouvez nettoyer des champs individuels en définissant ceux à inclure ou exclure du champ _source. {docsLink}", "xpack.idxMgmt.mappingsEditor.typeField.documentationLinkLabel": "Documentation de {typeName}", "xpack.idxMgmt.openIndicesAction.indexOpenedMessage": "L'index {indexNames} a été ouvert.", - "xpack.idxMgmt.openIndicesAction.successfullyOpenedIndicesMessage": "Ouverture réussie : [{indexNames}]", "xpack.idxMgmt.refreshIndicesAction.indexRefreshedMessage": "L'index {indexNames} a été actualisé.", - "xpack.idxMgmt.refreshIndicesAction.successfullyRefreshedIndicesMessage": "Actualisation réussie : [{indexNames}]", "xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "{numIndexPatterns, plural, one {Modèle} many {Modèles d''indexation manquants} other {Modèles}} d'index", "xpack.idxMgmt.templateForm.stepLogistics.dataStreamDescription": "Le modèle crée des flux de données au lieu d'index. {docsLink}", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "Les espaces et les caractères {invalidCharactersList} ne sont pas autorisés.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 02d57b484862e..564e643b52a7d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18634,9 +18634,7 @@ "xpack.grokDebugger.unknownErrorTitle": "問題が発生しました", "xpack.idxMgmt.badgeAriaLabel": "{label}。選択すると、これをフィルタリングします。", "xpack.idxMgmt.clearCacheIndicesAction.indexCacheClearedMessage": "インデックス{indexNames}のキャッシュがクリアされました。", - "xpack.idxMgmt.clearCacheIndicesAction.successMessage": "キャッシュが削除されました:[{indexNames}]", "xpack.idxMgmt.closeIndicesAction.indexClosedMessage": "インデックス{indexNames}は閉じられました。", - "xpack.idxMgmt.closeIndicesAction.successfullyClosedIndicesMessage": "[{indexNames}] がクローズされました", "xpack.idxMgmt.componentTemplateDetails.summaryTab.notInUseDescription": "インデックステンプレートを{createLink}するか、既存のテンプレートを{editLink}してください。", "xpack.idxMgmt.componentTemplateForm.stepLogistics.metaDescription": "クラスター状態に格納された、テンプレートに関する任意の情報。{learnMoreLink}", "xpack.idxMgmt.componentTemplateForm.stepLogistics.metaHelpText": "JSONフォーマットを使用:{code}", @@ -18655,7 +18653,6 @@ "xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText": "{count}データストリームの削除エラー", "xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText": "{numSuccesses, plural, other {#個のデータストリーム}}が削除されました", "xpack.idxMgmt.deleteIndicesAction.indexDeletedMessage": "インデックス{indexNames}が削除されました。", - "xpack.idxMgmt.deleteIndicesAction.successfullyDeletedIndicesMessage": "[{indexNames}] が削除されました", "xpack.idxMgmt.deleteTemplatesModal.confirmButtonLabel": "{numTemplatesToDelete, plural, other {テンプレート}}削除", "xpack.idxMgmt.deleteTemplatesModal.deleteDescription": "{numTemplatesToDelete, plural, other {これらのテンプレート}}を削除しようとしています:", "xpack.idxMgmt.deleteTemplatesModal.modalTitleText": "{numTemplatesToDelete, plural, other {#個のテンプレート}}削除", @@ -18673,9 +18670,7 @@ "xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryHelpText": "デフォルトは{code}クエリです。", "xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeTypePopOver": "{type}は、番号、日付、またはIPアドレスの範囲と一致します。", "xpack.idxMgmt.flushIndicesAction.indexFlushedMessage": "インデックス{indexNames}がフラッシュされました。", - "xpack.idxMgmt.flushIndicesAction.successfullyFlushedIndicesMessage": "[{indexNames}]がフラッシュされました", "xpack.idxMgmt.forceMergeIndicesAction.indexForcemergedMessage": "インデックス{indexNames}は強制的にマージされました。", - "xpack.idxMgmt.forceMergeIndicesAction.successfullyForceMergedIndicesMessage": "[{indexNames}]が強制結合されました", "xpack.idxMgmt.formWizard.stepAliases.aliasesEditorHelpText": "JSONフォーマットを使用:{code}", "xpack.idxMgmt.formWizard.stepSettings.settingsEditorHelpText": "JSONフォーマットを使用:{code}", "xpack.idxMgmt.goToDiscover.showIndexToolTip": "Discoverで{indexName}を表示", @@ -18772,9 +18767,7 @@ "xpack.idxMgmt.mappingsEditor.sourceFieldDescription": "_source フィールドには、インデックスの時点で指定された元の JSON ドキュメント本文が含まれています。_sourceフィールドに含めるか除外するフィールドを定義することで、_sourceフィールドから個別のフィールドを削除することができます。{docsLink}", "xpack.idxMgmt.mappingsEditor.typeField.documentationLinkLabel": "{typeName} ドキュメント", "xpack.idxMgmt.openIndicesAction.indexOpenedMessage": "インデックス{indexNames}は開かれました。", - "xpack.idxMgmt.openIndicesAction.successfullyOpenedIndicesMessage": "[{indexNames}] が開かれました", "xpack.idxMgmt.refreshIndicesAction.indexRefreshedMessage": "インデックス{indexNames}は更新されました。", - "xpack.idxMgmt.refreshIndicesAction.successfullyRefreshedIndicesMessage": "[{indexNames}] が更新されました", "xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "インデックス{numIndexPatterns, plural, other {パターン}}", "xpack.idxMgmt.templateForm.stepLogistics.dataStreamDescription": "テンプレートは、インデックスではなく、データストリームを作成します。{docsLink}", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "スペースと{invalidCharactersList}文字は使用できません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3b8d3a72ec401..99db38f64c037 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18634,9 +18634,7 @@ "xpack.grokDebugger.unknownErrorTitle": "出问题了", "xpack.idxMgmt.badgeAriaLabel": "{label}。选择以基于其进行筛选。", "xpack.idxMgmt.clearCacheIndicesAction.indexCacheClearedMessage": "已清除索引 {indexNames} 的缓存。", - "xpack.idxMgmt.clearCacheIndicesAction.successMessage": "已成功清除缓存:[{indexNames}]", "xpack.idxMgmt.closeIndicesAction.indexClosedMessage": "索引 {indexNames} 已关闭。", - "xpack.idxMgmt.closeIndicesAction.successfullyClosedIndicesMessage": "已成功关闭:[{indexNames}]", "xpack.idxMgmt.componentTemplateDetails.summaryTab.notInUseDescription": "{createLink}索引模板或{editLink}现有索引模板。", "xpack.idxMgmt.componentTemplateForm.stepLogistics.metaDescription": "有关模板的任意信息,以集群状态存储。{learnMoreLink}", "xpack.idxMgmt.componentTemplateForm.stepLogistics.metaHelpText": "使用 JSON 格式:{code}", @@ -18655,7 +18653,6 @@ "xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText": "删除 {count} 个数据流时出错", "xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText": "已删除 {numSuccesses, plural, other {# 个数据流}}", "xpack.idxMgmt.deleteIndicesAction.indexDeletedMessage": "索引 {indexNames} 已删除。", - "xpack.idxMgmt.deleteIndicesAction.successfullyDeletedIndicesMessage": "已成功删除:[{indexNames}]", "xpack.idxMgmt.deleteTemplatesModal.confirmButtonLabel": "删除 {numTemplatesToDelete, plural, other {模板}}", "xpack.idxMgmt.deleteTemplatesModal.deleteDescription": "您即将删除{numTemplatesToDelete, plural, other {以下模板}}:", "xpack.idxMgmt.deleteTemplatesModal.modalTitleText": "删除 {numTemplatesToDelete, plural, other {# 个模板}}", @@ -18673,9 +18670,7 @@ "xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryHelpText": "默认为:{code} 查询。", "xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeTypePopOver": "{type} 匹配一个数字、日期或 IP 地址范围。", "xpack.idxMgmt.flushIndicesAction.indexFlushedMessage": "索引 {indexNames} 已清空。", - "xpack.idxMgmt.flushIndicesAction.successfullyFlushedIndicesMessage": "已成功清空:[{indexNames}]", "xpack.idxMgmt.forceMergeIndicesAction.indexForcemergedMessage": "已强制合并索引 {indexNames}。", - "xpack.idxMgmt.forceMergeIndicesAction.successfullyForceMergedIndicesMessage": "已成功强制合并:[{indexNames}]", "xpack.idxMgmt.formWizard.stepAliases.aliasesEditorHelpText": "使用 JSON 格式:{code}", "xpack.idxMgmt.formWizard.stepSettings.settingsEditorHelpText": "使用 JSON 格式:{code}", "xpack.idxMgmt.goToDiscover.showIndexToolTip": "在 Discover 中显示 {indexName}", @@ -18772,9 +18767,7 @@ "xpack.idxMgmt.mappingsEditor.sourceFieldDescription": "_source 字段包含在索引时提供的原始 JSON 文档正文。单个字段可通过定义哪些字段可以在 _source 字段中包括或排除来进行修剪。{docsLink}", "xpack.idxMgmt.mappingsEditor.typeField.documentationLinkLabel": "{typeName} 文档", "xpack.idxMgmt.openIndicesAction.indexOpenedMessage": "索引 {indexNames} 已打开。", - "xpack.idxMgmt.openIndicesAction.successfullyOpenedIndicesMessage": "已成功打开:[{indexNames}]", "xpack.idxMgmt.refreshIndicesAction.indexRefreshedMessage": "索引 {indexNames} 已刷新。", - "xpack.idxMgmt.refreshIndicesAction.successfullyRefreshedIndicesMessage": "已成功刷新:[{indexNames}]", "xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "索引{numIndexPatterns, plural, other {模式}}", "xpack.idxMgmt.templateForm.stepLogistics.dataStreamDescription": "该模板创建数据流,而非索引。{docsLink}", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "不允许使用空格和字符 {invalidCharactersList}。", diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts index 70cdc0f96dfdd..60eb8b6634a35 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts @@ -13,6 +13,8 @@ import { } from '@kbn/actions-simulators-plugin/server/bedrock_simulation'; import { DEFAULT_TOKEN_LIMIT } from '@kbn/stack-connectors-plugin/common/bedrock/constants'; import { PassThrough } from 'stream'; +import { EventStreamCodec } from '@smithy/eventstream-codec'; +import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { getUrlPrefix, ObjectRemover } from '../../../../../common/lib'; @@ -411,8 +413,6 @@ export default function bedrockTest({ getService }: FtrProviderContext) { it('should invoke stream with assistant AI body argument formatted to bedrock expectations', async () => { await new Promise((resolve, reject) => { - let responseBody: string = ''; - const passThrough = new PassThrough(); supertest @@ -434,13 +434,14 @@ export default function bedrockTest({ getService }: FtrProviderContext) { assistantLangChain: false, }) .pipe(passThrough); - + const responseBuffer: Uint8Array[] = []; passThrough.on('data', (chunk) => { - responseBody += chunk.toString(); + responseBuffer.push(chunk); }); passThrough.on('end', () => { - expect(responseBody).to.eql('Hello world, what a unique string!'); + const parsed = parseBedrockBuffer(responseBuffer); + expect(parsed).to.eql('Hello world, what a unique string!'); resolve(); }); }); @@ -517,3 +518,46 @@ export default function bedrockTest({ getService }: FtrProviderContext) { }); }); } + +const parseBedrockBuffer = (chunks: Uint8Array[]): string => { + let bedrockBuffer: Uint8Array = new Uint8Array(0); + + return chunks + .map((chunk) => { + bedrockBuffer = concatChunks(bedrockBuffer, chunk); + let messageLength = getMessageLength(bedrockBuffer); + const buildChunks = []; + while (bedrockBuffer.byteLength > 0 && bedrockBuffer.byteLength >= messageLength) { + const extractedChunk = bedrockBuffer.slice(0, messageLength); + buildChunks.push(extractedChunk); + bedrockBuffer = bedrockBuffer.slice(messageLength); + messageLength = getMessageLength(bedrockBuffer); + } + + const awsDecoder = new EventStreamCodec(toUtf8, fromUtf8); + + return buildChunks + .map((bChunk) => { + const event = awsDecoder.decode(bChunk); + const body = JSON.parse( + Buffer.from(JSON.parse(new TextDecoder().decode(event.body)).bytes, 'base64').toString() + ); + return body.completion; + }) + .join(''); + }) + .join(''); +}; + +function concatChunks(a: Uint8Array, b: Uint8Array): Uint8Array { + const newBuffer = new Uint8Array(a.length + b.length); + newBuffer.set(a); + newBuffer.set(b, a.length); + return newBuffer; +} + +function getMessageLength(buffer: Uint8Array): number { + if (buffer.byteLength === 0) return 0; + const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + return view.getUint32(0, false); +} diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts index e7f17ddcc8cb2..927e1f58a1be3 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts @@ -30,7 +30,7 @@ import { openFirstAlert, } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts index 162c63ad3ce48..4fb4d50e7c6d9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts @@ -13,7 +13,7 @@ import { updateAlertTags, } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visitWithTimeRange } from '../../../tasks/navigation'; import { ALERTS_URL } from '../../../urls/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_via_fleet.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_via_fleet.cy.ts index 630bd099a1d0c..6da3d58c0530d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_via_fleet.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_via_fleet.cy.ts @@ -8,12 +8,13 @@ import type { BulkInstallPackageInfo } from '@kbn/fleet-plugin/common'; import type { Rule } from '@kbn/security-solution-plugin/public/detection_engine/rule_management/logic/types'; -import { resetRulesTableState, deleteAlertsAndRules } from '../../../tasks/common'; +import { resetRulesTableState } from '../../../tasks/common'; import { INSTALL_ALL_RULES_BUTTON, TOASTER } from '../../../screens/alerts_detection_rules'; import { getRuleAssets } from '../../../tasks/api_calls/prebuilt_rules'; import { login } from '../../../tasks/login'; import { clickAddElasticRulesButton } from '../../../tasks/prebuilt_rules'; import { visitRulesManagementTable } from '../../../tasks/rules_management'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; describe( 'Detection rules, Prebuilt Rules Installation and Update workflow', diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_workflow.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_workflow.cy.ts index 15e77fad28d03..ec4615bcf59e4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_workflow.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_workflow.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { resetRulesTableState, deleteAlertsAndRules } from '../../../tasks/common'; +import { resetRulesTableState } from '../../../tasks/common'; import { createRuleAssetSavedObject } from '../../../helpers/rules'; import { getInstallSingleRuleButtonByRuleId, @@ -28,6 +28,7 @@ import { clickAddElasticRulesButton, } from '../../../tasks/prebuilt_rules'; import { visitRulesManagementTable } from '../../../tasks/rules_management'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; describe( 'Detection rules, Prebuilt Rules Installation and Update workflow', diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts index d5a3f3bb85326..f3101f513915f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts @@ -35,7 +35,7 @@ import { getAvailablePrebuiltRulesCount, preventPrebuiltRulesPackageInstallation, } from '../../../tasks/api_calls/prebuilt_rules'; -import { deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../../tasks/common'; +import { deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; import { RULES_MANAGEMENT_URL } from '../../../urls/rules_management'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts index 180c435c10213..92bf9e7f1471c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts @@ -12,16 +12,13 @@ import { RULES_UPDATES_TAB, } from '../../../screens/alerts_detection_rules'; import { deleteFirstRule } from '../../../tasks/alerts_detection_rules'; +import { deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../../tasks/api_calls/common'; import { installAllPrebuiltRulesRequest, installPrebuiltRuleAssets, createAndInstallMockedPrebuiltRules, } from '../../../tasks/api_calls/prebuilt_rules'; -import { - resetRulesTableState, - deleteAlertsAndRules, - deletePrebuiltRulesAssets, -} from '../../../tasks/common'; +import { resetRulesTableState } from '../../../tasks/common'; import { login } from '../../../tasks/login'; import { visitRulesManagementTable } from '../../../tasks/rules_management'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_preview.cy.ts index 0b49fe4bb1dec..6deeb6f5202c0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_preview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_preview.cy.ts @@ -26,12 +26,7 @@ import { } from '../../../tasks/api_calls/prebuilt_rules'; import { createSavedQuery, deleteSavedQueries } from '../../../tasks/api_calls/saved_queries'; import { fetchMachineLearningModules } from '../../../tasks/api_calls/machine_learning'; -import { - resetRulesTableState, - deleteAlertsAndRules, - postDataView, - deleteDataView, -} from '../../../tasks/common'; +import { resetRulesTableState } from '../../../tasks/common'; import { login } from '../../../tasks/login'; import { assertRuleInstallationSuccessToastShown, @@ -62,6 +57,11 @@ import { openRuleUpdatePreview, } from '../../../tasks/prebuilt_rules_preview'; import { visitRulesManagementTable } from '../../../tasks/rules_management'; +import { + deleteAlertsAndRules, + deleteDataView, + postDataView, +} from '../../../tasks/api_calls/common'; const TEST_ENV_TAGS = ['@ess', '@serverless']; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.ts index 2e38b4782b433..edeb8ac98623b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.ts @@ -15,11 +15,12 @@ import { UPGRADE_SELECTED_RULES_BUTTON, } from '../../../screens/alerts_detection_rules'; import { selectRulesByName } from '../../../tasks/alerts_detection_rules'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { installPrebuiltRuleAssets, createAndInstallMockedPrebuiltRules, } from '../../../tasks/api_calls/prebuilt_rules'; -import { resetRulesTableState, deleteAlertsAndRules } from '../../../tasks/common'; +import { resetRulesTableState } from '../../../tasks/common'; import { login } from '../../../tasks/login'; import { assertRulesNotPresentInRuleUpdatesTable, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts index 98080cb3b47e8..3053f8e5c5698 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts @@ -10,7 +10,11 @@ import { getSimpleCustomQueryRule } from '../../../objects/rule'; import { goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { deleteIndex, waitForNewDocumentToBeIndexed } from '../../../tasks/api_calls/elasticsearch'; -import { deleteAlertsAndRules, deleteConnectors, deleteDataView } from '../../../tasks/common'; +import { + deleteAlertsAndRules, + deleteConnectors, + deleteDataView, +} from '../../../tasks/api_calls/common'; import { createAndEnableRule, fillAboutRuleAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_complete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_complete.cy.ts index 23421b218bcb6..13c35a3cce6c4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_complete.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_complete.cy.ts @@ -18,7 +18,7 @@ import { import { createRule } from '../../../tasks/api_calls/rules'; import { RULES_MANAGEMENT_URL } from '../../../urls/rules_management'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { goToActionsStepTab } from '../../../tasks/create_new_rule'; import { login } from '../../../tasks/login'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_essentials.cy.ts index 83503ea98738d..d36cdc7137de6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_essentials.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions_pli_essentials.cy.ts @@ -18,7 +18,7 @@ import { import { createRule } from '../../../tasks/api_calls/rules'; import { RULES_MANAGEMENT_URL } from '../../../urls/rules_management'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { goToActionsStepTab } from '../../../tasks/create_new_rule'; import { login } from '../../../tasks/login'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/common_flows.cy.ts index e8780d8696d29..9628f03f2d102 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/common_flows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/common_flows.cy.ts @@ -19,7 +19,7 @@ import { } from '../../../screens/create_new_rule'; import { RULE_NAME_HEADER } from '../../../screens/rule_details'; import { createTimeline } from '../../../tasks/api_calls/timelines'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { createAndEnableRule, expandAdvancedSettings, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts index e4bb4b2bfba83..5e41440f48f4e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts @@ -8,7 +8,7 @@ import { getNewRule } from '../../../objects/rule'; import { RULE_NAME_HEADER } from '../../../screens/rule_details'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { fillScheduleRuleAndContinue, fillAboutRuleMinimumAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts index d13a676e84253..7a6d1fa889e58 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts @@ -52,7 +52,11 @@ import { getRulesManagementTableRows, goToRuleDetailsOf, } from '../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules, deleteDataView, postDataView } from '../../../tasks/common'; +import { + deleteAlertsAndRules, + deleteDataView, + postDataView, +} from '../../../tasks/api_calls/common'; import { createAndEnableRule, createRuleWithoutEnabling, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts index 03ba3db7f25ff..f55a51d8e4f64 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts @@ -24,7 +24,7 @@ import { import { editFirstRule, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { createSavedQuery, deleteSavedQueries } from '../../../tasks/api_calls/saved_queries'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { createAndEnableRule, fillAboutRuleAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts index 2fa97e4f76e40..e543589bb44ce 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts @@ -18,7 +18,7 @@ import { ESQL_TYPE, ESQL_QUERY_BAR } from '../../../screens/create_new_rule'; import { getDetails, goBackToRulesTable } from '../../../tasks/rule_details'; import { expectNumberOfRules } from '../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { fillAboutRuleAndContinue, fillDefineEsqlRuleAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts index b895460661858..0966ae2709113 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts @@ -43,7 +43,7 @@ import { import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { createAndEnableRule, fillAboutRuleAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts index 6dbe81076c91e..2b83c938b9473 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts @@ -111,7 +111,7 @@ import { import { CREATE_RULE_URL } from '../../../urls/navigation'; import { RULES_MANAGEMENT_URL } from '../../../urls/rules_management'; import { openRuleManagementPageViaBreadcrumbs } from '../../../tasks/rules_management'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts index 8b8c6fe4e1457..570f19f3f72e1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts @@ -45,7 +45,7 @@ import { import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { createAndEnableRule, fillAboutRuleAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts index 585cd9187f3e0..9bb9f569c4dd1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts @@ -47,7 +47,7 @@ import { TIMESTAMP_OVERRIDE_DETAILS, } from '../../../screens/rule_details'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { createAndEnableRule, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts index 503b40f568303..62fdb9121c9c5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts @@ -45,7 +45,7 @@ import { import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { createAndEnableRule, fillAboutRuleAndContinue, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/common_flows.cy.ts index 5e5af0e6ad4a7..f5704122d9e33 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/common_flows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/common_flows.cy.ts @@ -45,7 +45,7 @@ import { } from '../../../screens/rule_details'; import { createTimeline } from '../../../tasks/api_calls/timelines'; -import { deleteAlertsAndRules, deleteConnectors } from '../../../tasks/common'; +import { deleteAlertsAndRules, deleteConnectors } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; import { ruleDetailsUrl } from '../../../urls/rule_details'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/esql_rule.cy.ts index 93100216692a4..7d1419e911e33 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details/esql_rule.cy.ts @@ -17,7 +17,7 @@ import { import { createRule } from '../../../tasks/api_calls/rules'; import { getDetails } from '../../../tasks/rule_details'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/custom_query_rule.cy.ts index ca6d6c56adcf7..e9497851d4cb0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/custom_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/custom_query_rule.cy.ts @@ -42,7 +42,7 @@ import { } from '../../../screens/rule_details'; import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules, deleteConnectors } from '../../../tasks/common'; +import { deleteAlertsAndRules, deleteConnectors } from '../../../tasks/api_calls/common'; import { addEmailConnectorAndRuleAction } from '../../../tasks/common/rule_actions'; import { fillAboutRule, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/esql_rule.cy.ts index eb16d89a6af8c..20d48b211995e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit/esql_rule.cy.ts @@ -15,7 +15,7 @@ import { createRule } from '../../../tasks/api_calls/rules'; import { RULES_MANAGEMENT_URL } from '../../../urls/rules_management'; import { getDetails } from '../../../tasks/rule_details'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { clearEsqlQueryBar, fillEsqlQueryBar, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts index 1376e486790b7..51aa3f406b8ed 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts @@ -26,14 +26,14 @@ import { disableRelatedIntegrations, enableRelatedIntegrations, } from '../../../../tasks/api_calls/kibana_advanced_settings'; -import { deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { login } from '../../../../tasks/login'; import { visitRulesManagementTable } from '../../../../tasks/rules_management'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { installIntegrations, PackagePolicyWithoutAgentPolicyId, -} from '../../../../tasks/integrations'; +} from '../../../../tasks/api_calls/integrations'; import { disableAutoRefresh, openIntegrationsPopover, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts index fd6b28fbe57ec..dd053ab958aab 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { deleteAlertsAndRules } from '../../../../../tasks/api_calls/common'; import { goToRuleDetailsOf, expectManagementTableRules, @@ -21,7 +22,7 @@ import { login } from '../../../../../tasks/login'; import { visitRulesManagementTable } from '../../../../../tasks/rules_management'; import { createRule } from '../../../../../tasks/api_calls/rules'; -import { resetRulesTableState, deleteAlertsAndRules } from '../../../../../tasks/common'; +import { resetRulesTableState } from '../../../../../tasks/common'; import { getNewRule } from '../../../../../objects/rule'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts index 920c03529c112..94b6804b31193 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { deleteAlertsAndRules } from '../../../../../tasks/api_calls/common'; import { MODAL_CONFIRMATION_BTN, MODAL_CONFIRMATION_BODY, @@ -79,7 +80,7 @@ import { login } from '../../../../../tasks/login'; import { visitRulesManagementTable } from '../../../../../tasks/rules_management'; import { createRule } from '../../../../../tasks/api_calls/rules'; import { loadPrepackagedTimelineTemplates } from '../../../../../tasks/api_calls/timelines'; -import { resetRulesTableState, deleteAlertsAndRules } from '../../../../../tasks/common'; +import { resetRulesTableState } from '../../../../../tasks/common'; import { getEqlRule, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts index ac6c723dbc0c6..62acef933f032 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts @@ -20,7 +20,7 @@ import { } from '../../../../../screens/rules_bulk_actions'; import { actionFormSelector } from '../../../../../screens/common/rule_actions'; -import { deleteAlertsAndRules, deleteConnectors } from '../../../../../tasks/common'; +import { deleteAlertsAndRules, deleteConnectors } from '../../../../../tasks/api_calls/common'; import type { RuleActionCustomFrequency } from '../../../../../tasks/common/rule_actions'; import { addSlackRuleAction, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts index 21adb447d2ce3..3b9ddf73ad3c7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts @@ -39,7 +39,11 @@ import { login } from '../../../../../tasks/login'; import { visitRulesManagementTable } from '../../../../../tasks/rules_management'; import { createRule } from '../../../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules, deleteDataView, postDataView } from '../../../../../tasks/common'; +import { + deleteAlertsAndRules, + deleteDataView, + postDataView, +} from '../../../../../tasks/api_calls/common'; import { getEqlRule, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/deletion/rule_delete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/deletion/rule_delete.cy.ts index 0896438e275e3..4c9168744920d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/deletion/rule_delete.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/deletion/rule_delete.cy.ts @@ -18,7 +18,7 @@ import { } from '../../../../../tasks/alerts_detection_rules'; import { deleteSelectedRules } from '../../../../../tasks/rules_bulk_actions'; import { createRule, findAllRules } from '../../../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../../tasks/api_calls/common'; import { login } from '../../../../../tasks/login'; describe('Rule deletion', { tags: ['@ess', '@serverless'] }, () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts index 0fb3d6f08613f..0cfcc43714497 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts @@ -7,6 +7,7 @@ import path from 'path'; +import { deleteAlertsAndRules } from '../../../../../tasks/api_calls/common'; import { expectedExportedRule, getNewRule } from '../../../../../objects/rule'; import { TOASTER_BODY, @@ -29,7 +30,7 @@ import { } from '../../../../../tasks/api_calls/exceptions'; import { getExceptionList } from '../../../../../objects/exception'; import { createRule } from '../../../../../tasks/api_calls/rules'; -import { resetRulesTableState, deleteAlertsAndRules } from '../../../../../tasks/common'; +import { resetRulesTableState } from '../../../../../tasks/common'; import { login } from '../../../../../tasks/login'; import { visit } from '../../../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts index 197ad5a9a82f5..4b8fe5b5312b0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts @@ -11,7 +11,7 @@ import { importRules, importRulesWithOverwriteAll, } from '../../../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../../tasks/api_calls/common'; import { deleteExceptionList } from '../../../../../tasks/api_calls/exceptions'; import { login } from '../../../../../tasks/login'; import { visit } from '../../../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts index 025aff9510d9d..7e753d42b6b6d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts @@ -9,7 +9,7 @@ import { INTERNAL_ALERTING_API_FIND_RULES_PATH } from '@kbn/alerting-plugin/comm import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { createRule, snoozeRule as snoozeRuleViaAPI } from '../../../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules, deleteConnectors } from '../../../../../tasks/common'; +import { deleteAlertsAndRules, deleteConnectors } from '../../../../../tasks/api_calls/common'; import { login } from '../../../../../tasks/login'; import { visitRulesManagementTable } from '../../../../../tasks/rules_management'; import { getNewRule } from '../../../../../objects/rule'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts index b76b862c70d02..117fc0eee632b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { resetRulesTableState, deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { resetRulesTableState } from '../../../../tasks/common'; import { login } from '../../../../tasks/login'; import { visitRulesManagementTable } from '../../../../tasks/rules_management'; import { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts index cf81271c1ad33..ace4406b1c22a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts @@ -8,7 +8,7 @@ import { getNewRule } from '../../../../objects/rule'; import { RULES_MONITORING_TAB, RULE_NAME } from '../../../../screens/alerts_detection_rules'; import { createRule } from '../../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/create_runtime_field.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/create_runtime_field.cy.ts index 2fd13f8b6696d..6838532d55938 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/create_runtime_field.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/create_runtime_field.cy.ts @@ -19,9 +19,9 @@ import { refreshPage } from '../../../tasks/security_header'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { createField } from '../../../tasks/create_runtime_field'; import { openAlertsFieldBrowser } from '../../../tasks/alerts'; -import { deleteRuntimeField } from '../../../tasks/sourcerer'; import { GET_DATA_GRID_HEADER } from '../../../screens/common/data_grid'; import { GET_TIMELINE_HEADER } from '../../../screens/timeline'; +import { deleteRuntimeField } from '../../../tasks/api_calls/sourcerer'; const alertRunTimeField = 'field.name.alert.page'; const timelineRuntimeField = 'field.name.timeline'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer.cy.ts index dbf5a5975f666..d27444e3d9a82 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer.cy.ts @@ -26,7 +26,7 @@ import { resetSourcerer, saveSourcerer, } from '../../../tasks/sourcerer'; -import { postDataView } from '../../../tasks/common'; +import { postDataView } from '../../../tasks/api_calls/common'; import { SOURCERER } from '../../../screens/sourcerer'; const siemDataViewTitle = 'Security Default Data View'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer_permissions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer_permissions.cy.ts index 52b8ccee82156..ce6b7e753d108 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer_permissions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/sourcerer/sourcerer_permissions.cy.ts @@ -9,7 +9,7 @@ import { loginWithUser } from '../../../tasks/login'; import { visitWithUser } from '../../../tasks/navigation'; import { hostsUrl } from '../../../urls/navigation'; -import { postDataView } from '../../../tasks/common'; +import { postDataView } from '../../../tasks/api_calls/common'; import { createUsersAndRoles, secReadCasesAll, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts index fe8d51cae795c..60b93650048d9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts @@ -17,7 +17,7 @@ import { import { ENRICHED_DATA_ROW } from '../../screens/alerts_details'; import { createRule } from '../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../tasks/common'; +import { deleteAlertsAndRules } from '../../tasks/api_calls/common'; import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { expandFirstAlert, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts index 8914b0c98076b..c4b605b85dcb6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { expandFirstAlert, goToClosedAlertsOnRuleDetailsPage, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts index ee10a2e702b0e..8dccaa04bdc87 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts @@ -26,7 +26,7 @@ import { import { login } from '../../../../tasks/login'; import { goToExceptionsTab, visitRuleDetailsPage } from '../../../../tasks/rule_details'; -import { deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { ADD_AND_BTN, ENTRY_DELETE_BTN, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts index 986bcb107124d..93e79ba9fa53e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts @@ -10,7 +10,7 @@ import { goToClosedAlertsOnRuleDetailsPage, waitForAlerts, } from '../../../../tasks/alerts'; -import { deleteAlertsAndRules, postDataView } from '../../../../tasks/common'; +import { deleteAlertsAndRules, postDataView } from '../../../../tasks/api_calls/common'; import { login } from '../../../../tasks/login'; import { visitRuleDetailsPage } from '../../../../tasks/rule_details'; import { createRule } from '../../../../tasks/api_calls/rules'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts index 3c613f32d2c73..72c18b27a9b2a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts @@ -47,7 +47,7 @@ import { FIELD_INPUT_PARENT, } from '../../../screens/exceptions'; -import { deleteAlertsAndRules, reload } from '../../../tasks/common'; +import { reload } from '../../../tasks/common'; import { createExceptionList, createExceptionListItem, @@ -55,6 +55,7 @@ import { deleteExceptionList, } from '../../../tasks/api_calls/exceptions'; import { getExceptionList } from '../../../objects/exception'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; // TODO: https://github.com/elastic/kibana/issues/161539 // Test Skipped until we fix the Flyout rerendering issue diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/match_any.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/match_any.cy.ts index f18b056c4e254..282e8d3c81223 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/match_any.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/match_any.cy.ts @@ -26,7 +26,7 @@ import { submitNewExceptionItem, } from '../../../tasks/exceptions'; import { CONFIRM_BTN } from '../../../screens/exceptions'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { ALERTS_COUNT } from '../../../screens/alerts'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts index 136038f641ec9..511343abc8a76 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts @@ -25,7 +25,7 @@ import { EXCEPTION_ITEM_VIEWER_CONTAINER, } from '../../../screens/exceptions'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; describe( 'Add multiple conditions and validate the generated exceptions', diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts index 8ec40a0e36436..e75c0eb8d81b0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts @@ -30,7 +30,7 @@ import { deleteAlertsAndRules, deleteEndpointExceptionList, deleteExceptionLists, -} from '../../../tasks/common'; +} from '../../../tasks/api_calls/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, EXCEPTION_ITEM_VIEWER_CONTAINER, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts index 6cc022873aea5..a06b76455dfbc 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts @@ -37,7 +37,7 @@ import { submitEditedExceptionItem, submitNewExceptionItem, } from '../../../tasks/exceptions'; -import { deleteAlertsAndRules, deleteExceptionLists } from '../../../tasks/common'; +import { deleteAlertsAndRules, deleteExceptionLists } from '../../../tasks/api_calls/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, EXCEPTION_ITEM_VIEWER_CONTAINER, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts index 79f6638e6c0f7..a4f0daa190e49 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts @@ -28,7 +28,7 @@ import { waitForTheRuleToBeExecuted, } from '../../../tasks/rule_details'; -import { postDataView, deleteAlertsAndRules } from '../../../tasks/common'; +import { postDataView, deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, EXCEPTION_ITEM_VIEWER_CONTAINER, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts index 935668db1a5a6..9002e9569b4fd 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts @@ -13,7 +13,7 @@ import { login } from '../../../tasks/login'; import { visitRulesManagementTable } from '../../../tasks/rules_management'; import { goToExceptionsTab, goToAlertsTab } from '../../../tasks/rule_details'; import { goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, EXCEPTION_ITEM_VIEWER_CONTAINER, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts index 9f64fac5f1512..2b9b200ae6c26 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts @@ -40,7 +40,7 @@ import { findSharedExceptionListItemsByName, } from '../../../tasks/exceptions_table'; import { visitRuleDetailsPage } from '../../../tasks/rule_details'; -import { deleteEndpointExceptionList, deleteExceptionLists } from '../../../tasks/common'; +import { deleteEndpointExceptionList, deleteExceptionLists } from '../../../tasks/api_calls/common'; // https://github.com/elastic/kibana/issues/171235 // FLAKY: https://github.com/elastic/kibana/issues/171242 diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts index af8edaa017b81..a4c251617b5f8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { deleteAlertsAndRules, deleteExceptionLists } from '../../../../tasks/common'; +import { deleteAlertsAndRules, deleteExceptionLists } from '../../../../tasks/api_calls/common'; import { createRule } from '../../../../tasks/api_calls/rules'; import { getExceptionList } from '../../../../objects/exception'; import { assertNumberOfExceptionItemsExists } from '../../../../tasks/exceptions'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts index e040c98730986..c0a2295887a1f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts @@ -17,7 +17,7 @@ import { DESCRIPTION_INPUT, ADD_COMMENT_INPUT } from '../../../screens/create_ne import { getCase1 } from '../../../objects/case'; import { getTimeline } from '../../../objects/timeline'; import { createTimeline } from '../../../tasks/api_calls/timelines'; -import { deleteTimelines } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/api_calls/common'; import { createCase } from '../../../tasks/api_calls/cases'; describe('attach timeline to case', { tags: ['@ess', '@serverless'] }, () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts index aaa785cc8f392..31c2068b49db0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts @@ -10,7 +10,7 @@ import { getServiceNowConnector, getServiceNowITSMHealthResponse } from '../../. import { SERVICE_NOW_MAPPING } from '../../../screens/configure_cases'; import { goToEditExternalConnection } from '../../../tasks/all_cases'; -import { deleteAllCasesItems, deleteConnectors } from '../../../tasks/common'; +import { deleteAllCasesItems, deleteConnectors } from '../../../tasks/api_calls/common'; import { addServiceNowConnector, openAddNewConnectorOption, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/privileges.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/privileges.cy.ts index f1cdbc0d7af90..341a75fe4b6f1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/privileges.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/privileges.cy.ts @@ -9,7 +9,7 @@ import type { TestCaseWithoutTimeline } from '../../../objects/case'; import { ALL_CASES_CREATE_NEW_CASE_BTN, ALL_CASES_NAME } from '../../../screens/all_cases'; import { goToCreateNewCase } from '../../../tasks/all_cases'; -import { deleteAllCasesItems } from '../../../tasks/common'; +import { deleteAllCasesItems } from '../../../tasks/api_calls/common'; import { backToCases, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts index 7210bacd1aa77..e849e0408c073 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts @@ -11,7 +11,7 @@ import { visitWithTimeRange } from '../../../tasks/navigation'; import { ALERTS_URL, ENTITY_ANALYTICS_URL } from '../../../urls/navigation'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { ANOMALIES_TABLE, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/filters/pinned_filters.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/filters/pinned_filters.cy.ts index a9615f27984ea..516b776a86e3d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/filters/pinned_filters.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/filters/pinned_filters.cy.ts @@ -21,7 +21,7 @@ import { openKibanaNavigation, } from '../../../tasks/kibana_navigation'; import { ALERTS_PAGE } from '../../../screens/kibana_navigation'; -import { postDataView } from '../../../tasks/common'; +import { postDataView } from '../../../tasks/api_calls/common'; import { navigateToAlertsPageInServerless } from '../../../tasks/serverless/navigation'; describe('ESS - pinned filters', { tags: ['@ess'] }, () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts index 9095a0a6f6e29..86309e80fd7e9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts @@ -18,8 +18,9 @@ import { } from '../../../tasks/inspect'; import { login } from '../../../tasks/login'; import { visitWithTimeRange } from '../../../tasks/navigation'; -import { postDataView, waitForWelcomePanelToBeLoaded } from '../../../tasks/common'; +import { waitForWelcomePanelToBeLoaded } from '../../../tasks/common'; import { selectDataView } from '../../../tasks/sourcerer'; +import { postDataView } from '../../../tasks/api_calls/common'; const DATA_VIEW = 'auditbeat-*'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts index 9ab0d07569421..7e5e19b464c4a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -25,7 +25,7 @@ import { openTable, } from '../../../tasks/alerts_details'; import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { login } from '../../../tasks/login'; import { visit, visitWithTimeRange } from '../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts index f06fad0cd43ee..2dc4a360134b2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts @@ -32,7 +32,7 @@ import { parseAlertsCountToInt, } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts index dd29284f6c0ce..0f8bcdd5a4693 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts @@ -51,7 +51,7 @@ import { import { TOASTER } from '../../../screens/alerts_detection_rules'; import { setEndDate, setStartDate } from '../../../tasks/date_picker'; import { fillAddFilterForm, openAddFilterPopover } from '../../../tasks/search_bar'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; const customFilters = [ { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index 6e0b437f2e2e6..aaa8806362d06 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -51,7 +51,7 @@ import { openTakeActionButtonAndSelectItem, selectTakeActionItem, } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; -import { deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; import { createRule } from '../../../../tasks/api_calls/rules'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 33bcb93caacf0..fb2af6a16b022 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { collapseDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; import { createNewCaseFromExpandableFlyout, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts index aa320000a256c..c855beca9fdb6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts @@ -25,7 +25,7 @@ import { filterTableTabTable, toggleColumnTableTabTable, } from '../../../../tasks/expandable_flyout/alert_details_right_panel_table_tab'; -import { deleteAlertsAndRules } from '../../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; import { createRule } from '../../../../tasks/api_calls/rules'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts index 98d76d984512d..b90413cdfe751 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts @@ -12,7 +12,7 @@ import { ALERTS_URL } from '../../../urls/navigation'; import { ALERTS_HISTOGRAM_SERIES, ALERT_RULE_NAME, MESSAGE } from '../../../screens/alerts'; import { TIMELINE_VIEW_IN_ANALYZER } from '../../../screens/timeline'; import { selectAlertsHistogram } from '../../../tasks/alerts'; -import { deleteTimelines } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/api_calls/common'; import { createTimeline } from '../../../tasks/api_calls/timelines'; import { getTimeline } from '../../../objects/timeline'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts index e19e4ca043b3c..eb3881f8123a5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts @@ -31,7 +31,7 @@ import { import { QUERY_TAB_BUTTON, TIMELINE_DATA_PROVIDERS_CONTAINER } from '../../../screens/timeline'; import { waitForAlerts } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { investigateDashboardItemInTimeline } from '../../../tasks/dashboards/common'; import { waitToNavigateAwayFrom } from '../../../tasks/kibana_navigation'; import { login } from '../../../tasks/login'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts index b3ed43ffe9d8a..bb1e0f372e33c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts @@ -26,7 +26,7 @@ import { TIMELINES_FAVORITE, } from '../../../screens/timelines'; import { createTimeline } from '../../../tasks/api_calls/timelines'; -import { deleteTimelines } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts index a50dbe08b0637..96b30a29d23d2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts @@ -21,7 +21,7 @@ import { addEqlToTimeline, saveTimeline } from '../../../tasks/timeline'; import { TIMELINES_URL } from '../../../urls/navigation'; import { EQL_QUERY_VALIDATION_ERROR } from '../../../screens/create_new_rule'; -import { deleteTimelines } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/api_calls/common'; describe('Correlation tab', { tags: ['@ess', '@serverless'] }, () => { const eql = 'any where process.name == "zsh"'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts index 546cdaa6b64c1..e324dede796f3 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts @@ -24,7 +24,7 @@ import { } from '../../../screens/timeline'; import { createTimelineTemplate } from '../../../tasks/api_calls/timelines'; -import { deleteTimelines } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visit, visitWithTimeRange } from '../../../tasks/navigation'; import { openTimelineUsingToggle } from '../../../tasks/security_main'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts index ba20f89defef7..80d4a9780c6e7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts @@ -16,7 +16,8 @@ import { TIMELINE_ROW_RENDERERS_SURICATA_LINK_TOOLTIP, TIMELINE_ROW_RENDERERS_MODAL_CLOSE_BUTTON, } from '../../../screens/timeline'; -import { deleteTimelines, waitForWelcomePanelToBeLoaded } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/api_calls/common'; +import { waitForWelcomePanelToBeLoaded } from '../../../tasks/common'; import { waitForAllHostsToBeLoaded } from '../../../tasks/hosts/all_hosts'; import { login } from '../../../tasks/login'; diff --git a/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts b/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts index e1dc4c952eac7..02ebebb6c10ea 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts @@ -6,7 +6,7 @@ */ import { Role } from '@kbn/security-plugin/common'; -import { rootRequest } from '../tasks/common'; +import { rootRequest } from '../tasks/api_calls/common'; /** * Utility function creates roles and corresponding users per each role with names diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/alerts.ts index 43d9952b9b376..3b9c0612a0724 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/alerts.ts @@ -9,7 +9,7 @@ import { RuleObjectId, RuleSignatureId, } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const DEFAULT_ALERTS_INDEX_PATTERN = '.alerts-security.alerts-*'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts new file mode 100644 index 0000000000000..775b4c5a8964b --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts @@ -0,0 +1,264 @@ +/* + * 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 { DATA_VIEW_PATH, INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { ELASTICSEARCH_PASSWORD, ELASTICSEARCH_USERNAME } from '../../env_var_names_constants'; +import { deleteAllDocuments } from './elasticsearch'; +import { DEFAULT_ALERTS_INDEX_PATTERN } from './alerts'; + +export const API_AUTH = Object.freeze({ + user: Cypress.env(ELASTICSEARCH_USERNAME), + pass: Cypress.env(ELASTICSEARCH_PASSWORD), +}); + +export const API_HEADERS = Object.freeze({ + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + [ELASTIC_HTTP_VERSION_HEADER]: [INITIAL_REST_VERSION], +}); + +export const rootRequest = ({ + headers: optionHeaders, + ...restOptions +}: Partial): Cypress.Chainable> => + cy.request({ + auth: API_AUTH, + headers: { + ...API_HEADERS, + ...(optionHeaders || {}), + }, + ...restOptions, + }); + +export const deleteAlertsAndRules = () => { + cy.log('Delete all alerts and rules'); + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + + rootRequest({ + method: 'POST', + url: '/api/detection_engine/rules/_bulk_action', + body: { + query: '', + action: 'delete', + }, + failOnStatusCode: false, + timeout: 300000, + }); + + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'alert', + }, + }, + ], + }, + }, + }, + }); + + deleteAllDocuments(`.lists-*,.items-*,${DEFAULT_ALERTS_INDEX_PATTERN}`); +}; + +export const deleteExceptionLists = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'exception-list', + }, + }, + ], + }, + }, + }, + }); +}; + +export const deleteEndpointExceptionList = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'exception-list-agnostic', + }, + }, + ], + }, + }, + }, + }); +}; + +export const deleteTimelines = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'siem-ui-timeline', + }, + }, + ], + }, + }, + }, + }); +}; + +export const deleteAlertsIndex = () => { + rootRequest({ + method: 'POST', + url: '/api/index_management/indices/delete', + body: { indices: ['.internal.alerts-security.alerts-default-000001'] }, + failOnStatusCode: false, + }); +}; + +export const deleteAllCasesItems = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_alerting_cases_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + term: { + type: 'cases', + }, + }, + { + term: { + type: 'cases-configure', + }, + }, + { + term: { + type: 'cases-comments', + }, + }, + { + term: { + type: 'cases-user-action', + }, + }, + { + term: { + type: 'cases-connector-mappings', + }, + }, + ], + }, + }, + ], + }, + }, + }, + }); +}; + +export const deleteConnectors = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_alerting_cases_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'action', + }, + }, + ], + }, + }, + }, + }); +}; + +export const deletePrebuiltRulesAssets = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + rootRequest({ + method: 'POST', + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'security-rule', + }, + }, + ], + }, + }, + }, + }); +}; + +export const postDataView = (indexPattern: string, name?: string, id?: string) => { + rootRequest({ + method: 'POST', + url: DATA_VIEW_PATH, + body: { + data_view: { + id: id || indexPattern, + name: name || indexPattern, + fieldAttrs: '{}', + title: indexPattern, + timeFieldName: '@timestamp', + }, + }, + failOnStatusCode: false, + }); +}; + +export const deleteDataView = (dataViewId: string) => { + rootRequest({ + method: 'POST', + url: 'api/content_management/rpc/delete', + body: { + contentTypeId: 'index-pattern', + id: dataViewId, + options: { force: true }, + version: 1, + }, + failOnStatusCode: false, + }); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts index d94049f14c8a1..6dc3622f72a03 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts @@ -4,17 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const deleteIndex = (index: string) => { rootRequest({ method: 'DELETE', url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); }; @@ -23,11 +18,6 @@ export const deleteDataStream = (dataStreamName: string) => { rootRequest({ method: 'DELETE', url: `${Cypress.env('ELASTICSEARCH_URL')}/_data_stream/${dataStreamName}`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); }; @@ -40,11 +30,6 @@ export const deleteAllDocuments = (target: string) => { url: `${Cypress.env( 'ELASTICSEARCH_URL' )}/${target}/_delete_by_query?conflicts=proceed&scroll_size=10000&refresh`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, body: { query: { match_all: {}, @@ -57,11 +42,6 @@ export const createIndex = (indexName: string, properties: Record({ method: 'GET', url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }).then((response) => { if (response.status !== 200) { @@ -110,11 +80,6 @@ export const refreshIndex = (index: string) => { rootRequest({ method: 'POST', url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_refresh`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }).then((response) => { if (response.status !== 200) { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts index 8b7d85b7ebf4d..4e0afa7416da0 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts @@ -13,17 +13,12 @@ import type { } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_LIST_ITEM_URL, ENDPOINT_LIST_URL } from '@kbn/securitysolution-list-constants'; import type { ExceptionList, ExceptionListItem, RuleExceptionItem } from '../../objects/exception'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const createEndpointExceptionList = () => rootRequest({ method: 'POST', url: ENDPOINT_LIST_URL, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); export const createEndpointExceptionListItem = (item: CreateEndpointListItemSchema) => @@ -31,11 +26,6 @@ export const createEndpointExceptionListItem = (item: CreateEndpointListItemSche method: 'POST', url: ENDPOINT_LIST_ITEM_URL, body: item, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); export const createExceptionList = ( @@ -51,11 +41,6 @@ export const createExceptionList = ( name: exceptionList.name, type: exceptionList.type, }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); @@ -88,11 +73,6 @@ export const createExceptionListItem = ( ], expire_time: exceptionListItem?.expire_time, }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); @@ -103,11 +83,6 @@ export const createRuleExceptionItem = (ruleId: string, exceptionListItems: Rule body: { items: exceptionListItems, }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); @@ -122,11 +97,6 @@ export const updateExceptionListItem = ( item_id: exceptionListItemId, ...exceptionListItemUpdate, }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); @@ -134,10 +104,5 @@ export const deleteExceptionList = (listId: string, namespaceType: string) => rootRequest({ method: 'DELETE', url: `/api/exception_lists?list_id=${listId}&namespace_type=${namespaceType}`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts index 5295b033fc7cf..d77361c95ebdd 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rootRequest } from '../common'; +import { rootRequest } from './common'; /** * Deletes all existing Fleet packages, package policies and agent policies. @@ -25,21 +25,11 @@ const deleteAgentPolicies = () => { return rootRequest<{ items: Array<{ id: string }> }>({ method: 'GET', url: 'api/fleet/agent_policies', - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }).then((response) => { response.body.items.forEach((item: { id: string }) => { rootRequest({ method: 'POST', url: `api/fleet/agent_policies/delete`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, body: { agentPolicyId: item.id, }, @@ -52,20 +42,10 @@ const deletePackagePolicies = () => { return rootRequest<{ items: Array<{ id: string }> }>({ method: 'GET', url: 'api/fleet/package_policies', - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }).then((response) => { rootRequest({ method: 'POST', url: `api/fleet/package_policies/delete`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, body: { packagePolicyIds: response.body.items.map((item: { id: string }) => item.id), }, @@ -77,22 +57,12 @@ const deletePackages = () => { return rootRequest<{ items: Array<{ status: string; name: string; version: string }> }>({ method: 'GET', url: 'api/fleet/epm/packages', - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }).then((response) => { response.body.items.forEach((item) => { if (item.status === 'installed') { rootRequest({ method: 'DELETE', url: `api/fleet/epm/packages/${item.name}/${item.version}`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); } }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/integrations.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/integrations.ts similarity index 83% rename from x-pack/test/security_solution_cypress/cypress/tasks/integrations.ts rename to x-pack/test/security_solution_cypress/cypress/tasks/api_calls/integrations.ts index eeef97682b9c7..4e212d4b8eb34 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/integrations.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/integrations.ts @@ -56,11 +56,6 @@ export function installIntegrations({ packages, force: true, }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); // Install agent and package policies @@ -68,11 +63,6 @@ export function installIntegrations({ method: 'POST', url: `${AGENT_POLICY_API_ROUTES.CREATE_PATTERN}?sys_monitoring=true`, body: agentPolicy, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }).then((response) => { const packagePolicyWithAgentPolicyId: PackagePolicy = { ...packagePolicy, @@ -83,11 +73,6 @@ export function installIntegrations({ method: 'POST', url: PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN, body: packagePolicyWithAgentPolicyId, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); }); } diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts index 2c982adad5275..27d5063ce30df 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts @@ -7,14 +7,13 @@ import { SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID } from '@kbn/management-settings-ids'; import { ENABLE_EXPANDABLE_FLYOUT_SETTING } from '@kbn/security-solution-plugin/common/constants'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const setKibanaSetting = (key: string, value: boolean | number | string) => { rootRequest({ method: 'POST', url: 'internal/kibana/settings', body: { changes: { [key]: value } }, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, }); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/machine_learning.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/machine_learning.ts index afb468876c5f1..f03d6edadbc18 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/machine_learning.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/machine_learning.ts @@ -7,17 +7,12 @@ import { ML_INTERNAL_BASE_PATH } from '@kbn/ml-plugin/common/constants/app'; import type { Module } from '@kbn/ml-plugin/common/types/modules'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const fetchMachineLearningModules = () => { return rootRequest({ method: 'GET', url: `${ML_INTERNAL_BASE_PATH}/modules/get_module`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': 1, - }, failOnStatusCode: false, }); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts index 98b9a884e683a..273491ebd2efe 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts @@ -13,17 +13,12 @@ import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common/d import type { PrePackagedRulesStatusResponse } from '@kbn/security-solution-plugin/public/detection_engine/rule_management/logic/types'; import { getPrebuiltRuleWithExceptionsMock } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; import { createRuleAssetSavedObject } from '../../helpers/rules'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const getPrebuiltRulesStatus = () => { return rootRequest({ method: 'GET', url: 'api/detection_engine/rules/prepackaged/_status', - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); }; @@ -43,14 +38,12 @@ export const installAllPrebuiltRulesRequest = () => rootRequest({ method: 'POST', url: PERFORM_RULE_INSTALLATION_URL, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '1', - }, body: { mode: 'ALL_RULES', }, + headers: { + 'elastic-api-version': '1', + }, }); /* Install specific prebuilt rules. Should be available as security-rule saved objects @@ -63,11 +56,6 @@ export const installSpecificPrebuiltRulesRequest = (rules: Array({ method: 'POST', url: PERFORM_RULE_INSTALLATION_URL, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '1', - }, body: { mode: 'SPECIFIC_RULES', rules: rules.map((rule) => ({ @@ -75,6 +63,9 @@ export const installSpecificPrebuiltRulesRequest = (rules: Array { @@ -128,8 +119,6 @@ export const createNewRuleAsset = ({ method: 'PUT', url, headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', 'Content-Type': 'application/json', }, failOnStatusCode: false, @@ -182,7 +171,7 @@ export const bulkCreateRuleAssets = ({ return rootRequest({ method: 'POST', url, - headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, failOnStatusCode: false, body: bulkIndexRequestBody, }).then((response) => response.status === 200); @@ -197,8 +186,6 @@ export const getRuleAssets = (index: string | undefined = '.kibana_security_solu method: 'GET', url, headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', 'Content-Type': 'application/json', }, failOnStatusCode: false, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts index 254d93cac5078..5f89e57ff81c6 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts @@ -16,16 +16,11 @@ import type { } from '@kbn/security-solution-plugin/common/api/detection_engine'; import type { FetchRulesResponse } from '@kbn/security-solution-plugin/public/detection_engine/rule_management/logic/types'; import { internalAlertingSnoozeRule } from '../../urls/routes'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const findAllRules = () => { return rootRequest({ url: DETECTION_ENGINE_RULES_URL_FIND, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); }; @@ -36,11 +31,6 @@ export const createRule = ( method: 'POST', url: DETECTION_ENGINE_RULES_URL, body: rule, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); }; @@ -52,7 +42,7 @@ export const createRule = ( * @param duration Snooze duration in milliseconds, -1 for indefinite */ export const snoozeRule = (id: string, duration: number): Cypress.Chainable => - cy.request({ + rootRequest({ method: 'POST', url: internalAlertingSnoozeRule(id), body: { @@ -61,7 +51,6 @@ export const snoozeRule = (id: string, duration: number): Cypress.Chainable => rRule: { dtstart: new Date().toISOString(), count: 1, tzid: moment().format('zz') }, }, }, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, failOnStatusCode: false, }); @@ -69,11 +58,6 @@ export const deleteCustomRule = (ruleId = '1') => { rootRequest({ method: 'DELETE', url: `api/detection_engine/rules?rule_id=${ruleId}`, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, failOnStatusCode: false, }); }; @@ -89,10 +73,7 @@ export const importRule = (ndjsonPath: string) => { url: 'api/detection_engine/rules/_import', method: 'POST', headers: { - 'kbn-xsrf': 'cypress-creds', 'content-type': 'multipart/form-data', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', }, body: formdata, }) @@ -108,10 +89,7 @@ export const waitForRulesToFinishExecution = (ruleIds: string[], afterDate?: Dat method: 'GET', url: DETECTION_ENGINE_RULES_URL_FIND, headers: { - 'kbn-xsrf': 'cypress-creds', 'content-type': 'multipart/form-data', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', }, }).then((response) => { const areAllRulesFinished = ruleIds.every((ruleId) => diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts index 88049b20d6d5b..0ec356e83727c 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { SavedQuery } from '@kbn/data-plugin/public'; import { SAVED_QUERY_BASE_URL } from '@kbn/data-plugin/common/constants'; -import { rootRequest } from '../common'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { rootRequest } from './common'; export const createSavedQuery = ( title: string, @@ -37,9 +37,7 @@ export const createSavedQuery = ( ], }, headers: { - 'kbn-xsrf': 'cypress-creds', [ELASTIC_HTTP_VERSION_HEADER]: '1', - 'x-elastic-internal-origin': 'security-solution', }, }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/sourcerer.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/sourcerer.ts new file mode 100644 index 0000000000000..a266678ee95d9 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/sourcerer.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { rootRequest } from './common'; + +export const deleteRuntimeField = (dataView: string, fieldName: string) => { + const deleteRuntimeFieldPath = `/api/data_views/data_view/${dataView}/runtime_field/${fieldName}`; + + rootRequest({ + url: deleteRuntimeFieldPath, + method: 'DELETE', + failOnStatusCode: false, + }); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts index 9b6a0c98db40c..a4edcc54752de 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts @@ -7,7 +7,7 @@ import type { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; import type { CompleteTimeline } from '../../objects/timeline'; -import { rootRequest } from '../common'; +import { rootRequest } from './common'; export const createTimeline = (timeline: CompleteTimeline) => rootRequest({ @@ -56,11 +56,6 @@ export const createTimeline = (timeline: CompleteTimeline) => : {}), }, }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - 'elastic-api-version': '2023-10-31', - }, }); export const createTimelineTemplate = (timeline: CompleteTimeline) => @@ -106,14 +101,12 @@ export const createTimelineTemplate = (timeline: CompleteTimeline) => savedQueryId: null, }, }, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, }); export const loadPrepackagedTimelineTemplates = () => rootRequest({ method: 'POST', url: 'api/timeline/_prepackaged', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, }); export const favoriteTimeline = ({ @@ -136,5 +129,4 @@ export const favoriteTimeline = ({ templateTimelineId: templateTimelineId || null, templateTimelineVersion: templateTimelineVersion || null, }, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts index a9b019fc1f6f6..b7d0062cd5d02 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts @@ -5,13 +5,8 @@ * 2.0. */ -import { DATA_VIEW_PATH, INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants'; -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { KIBANA_LOADING_ICON } from '../screens/security_header'; import { EUI_BASIC_TABLE_LOADING } from '../screens/common/controls'; -import { deleteAllDocuments } from './api_calls/elasticsearch'; -import { DEFAULT_ALERTS_INDEX_PATTERN } from './api_calls/alerts'; -import { ELASTICSEARCH_PASSWORD, ELASTICSEARCH_USERNAME } from '../env_var_names_constants'; const primaryButton = 0; @@ -21,30 +16,6 @@ const primaryButton = 0; */ const dndSloppyClickDetectionThreshold = 5; -export const API_AUTH = Object.freeze({ - user: Cypress.env(ELASTICSEARCH_USERNAME), - pass: Cypress.env(ELASTICSEARCH_PASSWORD), -}); - -export const API_HEADERS = Object.freeze({ - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - [ELASTIC_HTTP_VERSION_HEADER]: [INITIAL_REST_VERSION], -}); - -export const rootRequest = ({ - headers: optionHeaders, - ...restOptions -}: Partial): Cypress.Chainable> => - cy.request({ - auth: API_AUTH, - headers: { - ...API_HEADERS, - ...(optionHeaders || {}), - }, - ...restOptions, - }); - /** Starts dragging the subject */ export const drag = (subject: JQuery) => { const subjectLocation = subject[0].getBoundingClientRect(); @@ -99,243 +70,6 @@ export const resetRulesTableState = () => { clearSessionStorage(); }; -export const deleteAlertsAndRules = () => { - cy.log('Delete all alerts and rules'); - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - - rootRequest({ - method: 'POST', - url: '/api/detection_engine/rules/_bulk_action', - body: { - query: '', - action: 'delete', - }, - failOnStatusCode: false, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - }, - timeout: 300000, - }); - - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'alert', - }, - }, - ], - }, - }, - }, - }); - - deleteAllDocuments(`.lists-*,.items-*,${DEFAULT_ALERTS_INDEX_PATTERN}`); -}; - -export const deleteExceptionLists = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'exception-list', - }, - }, - ], - }, - }, - }, - }); -}; - -export const deleteEndpointExceptionList = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'exception-list-agnostic', - }, - }, - ], - }, - }, - }, - }); -}; - -export const deleteTimelines = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'siem-ui-timeline', - }, - }, - ], - }, - }, - }, - }); -}; - -export const deleteAlertsIndex = () => { - rootRequest({ - method: 'POST', - url: '/api/index_management/indices/delete', - body: { indices: ['.internal.alerts-security.alerts-default-000001'] }, - failOnStatusCode: false, - }); -}; - -export const deleteAllCasesItems = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_alerting_cases_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - bool: { - should: [ - { - term: { - type: 'cases', - }, - }, - { - term: { - type: 'cases-configure', - }, - }, - { - term: { - type: 'cases-comments', - }, - }, - { - term: { - type: 'cases-user-action', - }, - }, - { - term: { - type: 'cases-connector-mappings', - }, - }, - ], - }, - }, - ], - }, - }, - }, - }); -}; - -export const deleteConnectors = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_alerting_cases_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'action', - }, - }, - ], - }, - }, - }, - }); -}; - -export const deletePrebuiltRulesAssets = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'security-rule', - }, - }, - ], - }, - }, - }, - }); -}; - -export const postDataView = (indexPattern: string, name?: string, id?: string) => { - rootRequest({ - method: 'POST', - url: DATA_VIEW_PATH, - body: { - data_view: { - id: id || indexPattern, - name: name || indexPattern, - fieldAttrs: '{}', - title: indexPattern, - timeFieldName: '@timestamp', - }, - }, - headers: { - 'kbn-xsrf': 'cypress-creds', - 'x-elastic-internal-origin': 'security-solution', - }, - failOnStatusCode: false, - }); -}; - -export const deleteDataView = (dataViewId: string) => { - rootRequest({ - method: 'POST', - url: 'api/content_management/rpc/delete', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, - body: { - contentTypeId: 'index-pattern', - id: dataViewId, - options: { force: true }, - version: 1, - }, - failOnStatusCode: false, - }); -}; - export const scrollToBottom = () => cy.scrollTo('bottom'); export const waitForWelcomePanelToBeLoaded = () => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts index 4bf71413f8a57..4df97fd86461d 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts @@ -12,13 +12,13 @@ import { LoginState } from '@kbn/security-plugin/common/login_state'; import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { KNOWN_SERVERLESS_ROLE_DEFINITIONS } from '@kbn/security-solution-plugin/common/test'; import { LOGOUT_URL } from '../urls/navigation'; -import { API_HEADERS, rootRequest } from './common'; import { CLOUD_SERVERLESS, ELASTICSEARCH_PASSWORD, ELASTICSEARCH_USERNAME, IS_SERVERLESS, } from '../env_var_names_constants'; +import { API_HEADERS, rootRequest } from './api_calls/common'; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts b/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts index 39a6ed99b1b2e..a534fb3e6d3d8 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts @@ -9,7 +9,6 @@ import { DEFAULT_ALERTS_INDEX } from '@kbn/security-solution-plugin/common/const import { HOSTS_STAT, SOURCERER } from '../screens/sourcerer'; import { hostsUrl } from '../urls/navigation'; import { openTimelineUsingToggle } from './security_main'; -import { rootRequest } from './common'; import { visitWithTimeRange } from './navigation'; export const openSourcerer = (sourcererScope?: string) => { @@ -130,14 +129,3 @@ export const refreshUntilAlertsIndexExists = async () => { { interval: 500, timeout: 12000 } ); }; - -export const deleteRuntimeField = (dataView: string, fieldName: string) => { - const deleteRuntimeFieldPath = `/api/data_views/data_view/${dataView}/runtime_field/${fieldName}`; - - rootRequest({ - url: deleteRuntimeFieldPath, - method: 'DELETE', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, - failOnStatusCode: false, - }); -}; diff --git a/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml b/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml index a8f7adfc85777..8b0627d960a49 100644 --- a/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml +++ b/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml @@ -240,6 +240,7 @@ t3_analyst: privileges: - read - write + - maintenance - names: - .lists* - .items* @@ -293,12 +294,16 @@ threat_intelligence_analyst: - endgame-* - filebeat-* - logs-* - - .lists* - - .items* - packetbeat-* - winlogbeat-* privileges: - read + - names: + - .lists* + - .items* + privileges: + - read + - write - names: - .alerts-security* - .siem-signals-* @@ -317,8 +322,7 @@ threat_intelligence_analyst: - application: "kibana-.kibana" privileges: - feature_ml.read - - feature_siem.read - - feature_siem.read_alerts + - feature_siem.all - feature_siem.endpoint_list_read - feature_siem.blocklist_all - feature_securitySolutionCases.all @@ -603,6 +607,7 @@ endpoint_operations_analyst: privileges: - read - write + - maintenance applications: - application: "kibana-.kibana" privileges: @@ -653,11 +658,15 @@ endpoint_policy_manager: - logs-* - packetbeat-* - winlogbeat-* + - risk-score.risk-score-* + privileges: + - read + - names: - .lists* - .items* - - risk-score.risk-score-* privileges: - read + - write - names: - .alerts-security* - .siem-signals-*