diff --git a/.buildkite/pipeline-utils/buildkite/client.ts b/.buildkite/pipeline-utils/buildkite/client.ts index fe3f2041646e0..75a7585622b8c 100644 --- a/.buildkite/pipeline-utils/buildkite/client.ts +++ b/.buildkite/pipeline-utils/buildkite/client.ts @@ -7,16 +7,21 @@ */ import axios, { AxiosInstance } from 'axios'; -import { execSync } from 'child_process'; +import { execSync, ExecSyncOptions } from 'child_process'; import { dump } from 'js-yaml'; import { parseLinkHeader } from './parse_link_header'; import { Artifact } from './types/artifact'; import { Build, BuildStatus } from './types/build'; import { Job, JobState } from './types/job'; +type ExecType = + | ((command: string, execOpts: ExecSyncOptions) => Buffer | null) + | ((command: string, execOpts: ExecSyncOptions) => string | null); + export interface BuildkiteClientConfig { baseUrl?: string; token?: string; + exec?: ExecType; } export interface BuildkiteGroup { @@ -24,7 +29,9 @@ export interface BuildkiteGroup { steps: BuildkiteStep[]; } -export interface BuildkiteStep { +export type BuildkiteStep = BuildkiteCommandStep | BuildkiteInputStep; + +export interface BuildkiteCommandStep { command: string; label: string; parallelism?: number; @@ -43,6 +50,50 @@ export interface BuildkiteStep { env?: { [key: string]: string }; } +interface BuildkiteInputTextField { + text: string; + key: string; + hint?: string; + required?: boolean; + default?: string; +} + +interface BuildkiteInputSelectField { + select: string; + key: string; + hint?: string; + required?: boolean; + default?: string; + multiple?: boolean; + options: Array<{ + label: string; + value: string; + }>; +} + +export interface BuildkiteInputStep { + input: string; + prompt?: string; + fields: Array; + if?: string; + allow_dependency_failure?: boolean; + branches?: string; + parallelism?: number; + agents?: { + queue: string; + }; + timeout_in_minutes?: number; + key?: string; + depends_on?: string | string[]; + retry?: { + automatic: Array<{ + exit_status: string; + limit: number; + }>; + }; + env?: { [key: string]: string }; +} + export interface BuildkiteTriggerBuildParams { commit: string; branch: string; @@ -61,6 +112,7 @@ export interface BuildkiteTriggerBuildParams { export class BuildkiteClient { http: AxiosInstance; + exec: ExecType; constructor(config: BuildkiteClientConfig = {}) { const BUILDKITE_BASE_URL = @@ -78,6 +130,8 @@ export class BuildkiteClient { }, }); + this.exec = config.exec ?? execSync; + // this.agentHttp = axios.create({ // baseURL: BUILDKITE_AGENT_BASE_URL, // headers: { @@ -97,6 +151,32 @@ export class BuildkiteClient { return resp.data as Build; }; + getBuildsAfterDate = async ( + pipelineSlug: string, + date: string, + numberOfBuilds: number + ): Promise => { + const response = await this.http.get( + `v2/organizations/elastic/pipelines/${pipelineSlug}/builds?created_from=${date}&per_page=${numberOfBuilds}` + ); + return response.data as Build[]; + }; + + getBuildForCommit = async (pipelineSlug: string, commit: string): Promise => { + if (commit.length !== 40) { + throw new Error(`Invalid commit hash: ${commit}, this endpoint works with full SHAs only`); + } + + const response = await this.http.get( + `v2/organizations/elastic/pipelines/${pipelineSlug}/builds?commit=${commit}` + ); + const builds = response.data as Build[]; + if (builds.length === 0) { + return null; + } + return builds[0]; + }; + getCurrentBuild = (includeRetriedJobs = false) => { if (!process.env.BUILDKITE_PIPELINE_SLUG || !process.env.BUILDKITE_BUILD_NUMBER) { throw new Error( @@ -235,31 +315,46 @@ export class BuildkiteClient { }; setMetadata = (key: string, value: string) => { - execSync(`buildkite-agent meta-data set '${key}'`, { + this.exec(`buildkite-agent meta-data set '${key}'`, { input: value, stdio: ['pipe', 'inherit', 'inherit'], }); }; + getMetadata(key: string, defaultValue: string | null = null): string | null { + try { + const stdout = this.exec(`buildkite-agent meta-data get '${key}'`, { + stdio: ['pipe'], + }); + return stdout?.toString().trim() || defaultValue; + } catch (e) { + if (e.message.includes('404 Not Found')) { + return defaultValue; + } else { + throw e; + } + } + } + setAnnotation = ( context: string, style: 'info' | 'success' | 'warning' | 'error', value: string ) => { - execSync(`buildkite-agent annotate --context '${context}' --style '${style}'`, { + this.exec(`buildkite-agent annotate --context '${context}' --style '${style}'`, { input: value, stdio: ['pipe', 'inherit', 'inherit'], }); }; uploadArtifacts = (pattern: string) => { - execSync(`buildkite-agent artifact upload '${pattern}'`, { + this.exec(`buildkite-agent artifact upload '${pattern}'`, { stdio: ['ignore', 'inherit', 'inherit'], }); }; uploadSteps = (steps: Array) => { - execSync(`buildkite-agent pipeline upload`, { + this.exec(`buildkite-agent pipeline upload`, { input: dump({ steps }), stdio: ['pipe', 'inherit', 'inherit'], }); diff --git a/.buildkite/pipeline-utils/github/github.ts b/.buildkite/pipeline-utils/github/github.ts index fc6ab42a69a5e..ff1bd2e65ccfb 100644 --- a/.buildkite/pipeline-utils/github/github.ts +++ b/.buildkite/pipeline-utils/github/github.ts @@ -91,3 +91,7 @@ export const doAnyChangesMatch = async ( return anyFilesMatchRequired; }; + +export function getGithubClient() { + return github; +} diff --git a/.buildkite/pipeline-utils/index.ts b/.buildkite/pipeline-utils/index.ts index 113ab1ac2458f..b8da40de58f2e 100644 --- a/.buildkite/pipeline-utils/index.ts +++ b/.buildkite/pipeline-utils/index.ts @@ -10,3 +10,4 @@ export * from './buildkite'; export * as CiStats from './ci-stats'; export * from './github'; export * as TestFailures from './test-failures'; +export * from './utils'; diff --git a/.buildkite/pipeline-utils/utils.ts b/.buildkite/pipeline-utils/utils.ts new file mode 100644 index 0000000000000..e9a5cf9193334 --- /dev/null +++ b/.buildkite/pipeline-utils/utils.ts @@ -0,0 +1,24 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { execSync } from 'child_process'; + +const getKibanaDir = (() => { + let kibanaDir: string | undefined; + return () => { + if (!kibanaDir) { + kibanaDir = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }) + .toString() + .trim(); + } + + return kibanaDir; + }; +})(); + +export { getKibanaDir }; diff --git a/.buildkite/pipelines/flaky_tests/groups.json b/.buildkite/pipelines/flaky_tests/groups.json index 1a5799a862850..99f88fe590a1d 100644 --- a/.buildkite/pipelines/flaky_tests/groups.json +++ b/.buildkite/pipelines/flaky_tests/groups.json @@ -23,6 +23,23 @@ { "key": "cypress/security_serverless_explore", "name": "[Serverless] Security Solution Explore - Cypress" + }, + { + "key": "cypress/security_solution_rule_management", + "name": "Security Solution Rule Management - Cypress" + }, + { + "key": "cypress/security_serverless_rule_management", + "name": "[Serverless] Security Solution Rule Management - Cypress" + }, + + { + "key": "cypress/security_solution_rule_management_prebuilt_rules", + "name": "Security Solution Rule Management - Prebuilt Rules - Cypress" + }, + { + "key": "cypress/security_serverless_rule_management_prebuilt_rules", + "name": "[Serverless] Security Solution Rule Management - Prebuilt Rules - Cypress" }, { "key": "cypress/defend_workflows", diff --git a/.buildkite/pipelines/serverless_deployment/create_deployment_tag.yml b/.buildkite/pipelines/serverless_deployment/create_deployment_tag.yml new file mode 100644 index 0000000000000..d416b2f85ac16 --- /dev/null +++ b/.buildkite/pipelines/serverless_deployment/create_deployment_tag.yml @@ -0,0 +1,33 @@ +## Creates deploy@ tag on Kibana + +agents: + queue: kibana-default + +steps: + - label: "List potential commits" + commands: + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state initialize + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state collect_commits + - ts-node .buildkite/scripts/serverless/create_deploy_tag/list_commit_candidates.ts 25 + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state wait_for_selection + key: select_commit + + - wait: ~ + + - label: "Collect commit info" + commands: + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state collect_commit_info + - bash .buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.sh + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state wait_for_confirmation + key: collect_data + depends_on: select_commit + + - wait: ~ + + - label: ":ship: Create Deploy Tag" + commands: + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state create_deploy_tag + - bash .buildkite/scripts/serverless/create_deploy_tag/create_deploy_tag.sh + - ts-node .buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts --state tag_created + env: + DRY_RUN: $DRY_RUN diff --git a/.buildkite/scripts/lifecycle/pre_command.sh b/.buildkite/scripts/lifecycle/pre_command.sh index 965c09621caa0..10a43f4ac9b03 100755 --- a/.buildkite/scripts/lifecycle/pre_command.sh +++ b/.buildkite/scripts/lifecycle/pre_command.sh @@ -139,6 +139,9 @@ export SYNTHETICS_REMOTE_KIBANA_PASSWORD SYNTHETICS_REMOTE_KIBANA_URL=${SYNTHETICS_REMOTE_KIBANA_URL-"$(retry 5 5 vault read -field=url secret/kibana-issues/dev/kibana-ci-synthetics-remote-credentials)"} export SYNTHETICS_REMOTE_KIBANA_URL +DEPLOY_TAGGER_SLACK_WEBHOOK_URL=${DEPLOY_TAGGER_SLACK_WEBHOOK_URL:-"$(retry 5 5 vault read -field=DEPLOY_TAGGER_SLACK_WEBHOOK_URL secret/kibana-issues/dev/kibana-serverless-release-tools)"} +export DEPLOY_TAGGER_SLACK_WEBHOOK_URL + # Setup Failed Test Reporter Elasticsearch credentials { TEST_FAILURES_ES_CLOUD_ID=$(retry 5 5 vault read -field=cloud_id secret/kibana-issues/dev/failed_tests_reporter_es) diff --git a/.buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.sh b/.buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.sh new file mode 100755 index 0000000000000..752d7d4d3e53f --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# SO migration comparison lives in the Kibana dev app code, needs bootstrapping +.buildkite/scripts/bootstrap.sh + +echo "--- Collecting commit info" +ts-node .buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.ts + +cat << EOF | buildkite-agent pipeline upload + steps: + - block: "Confirm deployment" + prompt: "Are you sure you want to deploy to production? (dry run: ${DRY_RUN:-false})" + depends_on: collect_data +EOF diff --git a/.buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.ts b/.buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.ts new file mode 100644 index 0000000000000..ce30a3a71d8ee --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/collect_commit_info.ts @@ -0,0 +1,96 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { COMMIT_INFO_CTX, exec } from './shared'; +import { + toGitCommitExtract, + getCurrentQARelease, + getSelectedCommitHash, + getCommitByHash, + makeCommitInfoHtml, +} from './info_sections/commit_info'; +import { + getArtifactBuild, + getOnMergePRBuild, + getQAFBuildContainingCommit, + makeBuildkiteBuildInfoHtml, +} from './info_sections/build_info'; +import { + compareSOSnapshots, + makeSOComparisonBlockHtml, + makeSOComparisonErrorHtml, +} from './info_sections/so_snapshot_comparison'; +import { makeUsefulLinksHtml } from './info_sections/useful_links'; + +async function main() { + const previousSha = await getCurrentQARelease(); + const selectedSha = getSelectedCommitHash(); + + // Current commit info + const previousCommit = await getCommitByHash(previousSha); + const previousCommitInfo = toGitCommitExtract(previousCommit); + addBuildkiteInfoSection(makeCommitInfoHtml('Current commit on QA:', previousCommitInfo)); + + // Target commit info + const selectedCommit = await getCommitByHash(selectedSha); + const selectedCommitInfo = toGitCommitExtract(selectedCommit); + addBuildkiteInfoSection(makeCommitInfoHtml('Target commit to deploy:', selectedCommitInfo)); + + // Buildkite build info + const buildkiteBuild = await getOnMergePRBuild(selectedSha); + const nextBuildContainingCommit = await getQAFBuildContainingCommit( + selectedSha, + selectedCommitInfo.date! + ); + const artifactBuild = await getArtifactBuild(selectedSha); + addBuildkiteInfoSection( + makeBuildkiteBuildInfoHtml('Relevant build info:', { + 'Merge build': buildkiteBuild, + 'Artifact container build': artifactBuild, + 'Next QAF test build containing this commit': nextBuildContainingCommit, + }) + ); + + // Save Object migration comparison + const comparisonResult = compareSOSnapshots(previousSha, selectedSha); + if (comparisonResult) { + addBuildkiteInfoSection(makeSOComparisonBlockHtml(comparisonResult)); + } else { + addBuildkiteInfoSection(makeSOComparisonErrorHtml()); + } + + // Useful links + addBuildkiteInfoSection( + makeUsefulLinksHtml('Useful links:', { + previousCommitHash: previousSha, + selectedCommitHash: selectedSha, + }) + ); +} + +function addBuildkiteInfoSection(html: string) { + exec(`buildkite-agent annotate --append --style 'info' --context '${COMMIT_INFO_CTX}'`, { + input: html + '
', + }); +} + +main() + .then(() => { + console.log('Commit-related information added.'); + }) + .catch((error) => { + console.error(error); + process.exit(1); + }) + .finally(() => { + // When running locally, we can see what calls were made to execSync to debug + if (!process.env.CI) { + // @ts-ignore + console.log(exec.calls); + } + }); diff --git a/.buildkite/scripts/serverless/create_deploy_tag/create_deploy_tag.sh b/.buildkite/scripts/serverless/create_deploy_tag/create_deploy_tag.sh new file mode 100755 index 0000000000000..b0d7660054bce --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/create_deploy_tag.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +DEPLOY_TAG="deploy@$(date +%s)" +KIBANA_COMMIT_SHA=$(buildkite-agent meta-data get selected-commit-hash) + +if [[ -z "$KIBANA_COMMIT_SHA" ]]; then + echo "Commit sha is not set, exiting." + exit 1 +fi + +echo "--- Creating deploy tag $DEPLOY_TAG at $KIBANA_COMMIT_SHA" + +# Set git identity to whomever triggered the buildkite job +git config user.email "$BUILDKITE_BUILD_CREATOR_EMAIL" +git config user.name "$BUILDKITE_BUILD_CREATOR" + +# Create a tag for the deploy +git tag -a "$DEPLOY_TAG" "$KIBANA_COMMIT_SHA" \ + -m "Tagging release $KIBANA_COMMIT_SHA as: $DEPLOY_TAG, by $BUILDKITE_BUILD_CREATOR_EMAIL" + +# Set meta-data for the deploy tag +buildkite-agent meta-data set deploy-tag "$DEPLOY_TAG" + +# Push the tag to GitHub +if [[ -z "${DRY_RUN:-}" ]]; then + echo "Pushing tag to GitHub..." + git push origin --tags +else + echo "Skipping tag push to GitHub due to DRY_RUN=$DRY_RUN" +fi + +echo "Created deploy tag: $DEPLOY_TAG - your QA release should start @ https://buildkite.com/elastic/kibana-serverless-release/builds?branch=$DEPLOY_TAG" diff --git a/.buildkite/scripts/serverless/create_deploy_tag/info_sections/build_info.ts b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/build_info.ts new file mode 100644 index 0000000000000..7330458703546 --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/build_info.ts @@ -0,0 +1,194 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { components } from '@octokit/openapi-types'; +import { buildkite, buildkiteBuildStateToEmoji, CommitWithStatuses, octokit } from '../shared'; +import { Build } from '#pipeline-utils/buildkite'; + +const QA_FTR_TEST_SLUG = 'appex-qa-serverless-kibana-ftr-tests'; +const KIBANA_ARTIFACT_BUILD_SLUG = 'kibana-artifacts-container-image'; +const KIBANA_PR_BUILD_SLUG = 'kibana-on-merge'; + +export interface BuildkiteBuildExtract { + success: boolean; + stateEmoji: string; + url: string; + buildNumber: number; + slug: string; + commit: string; + startedAt: string; + finishedAt: string; + kibanaCommit: string; +} + +export async function getOnMergePRBuild(commitSha: string): Promise { + const buildkiteBuild = await buildkite.getBuildForCommit(KIBANA_PR_BUILD_SLUG, commitSha); + + if (!buildkiteBuild) { + return null; + } + + const stateEmoji = buildkiteBuildStateToEmoji(buildkiteBuild.state); + + return { + success: buildkiteBuild.state === 'passed', + stateEmoji, + slug: KIBANA_PR_BUILD_SLUG, + url: buildkiteBuild.web_url, + buildNumber: buildkiteBuild.number, + commit: commitSha, + kibanaCommit: buildkiteBuild.commit, + startedAt: buildkiteBuild.started_at, + finishedAt: buildkiteBuild.finished_at, + }; +} + +export async function getArtifactBuild(commitSha: string): Promise { + const build = await buildkite.getBuildForCommit(KIBANA_ARTIFACT_BUILD_SLUG, commitSha); + + if (!build) { + return null; + } + + return { + success: build.state === 'passed', + stateEmoji: buildkiteBuildStateToEmoji(build.state), + url: build.web_url, + slug: KIBANA_ARTIFACT_BUILD_SLUG, + buildNumber: build.number, + commit: build.commit, + kibanaCommit: build.commit, + startedAt: build.started_at, + finishedAt: build.finished_at, + }; +} + +export async function getQAFBuildContainingCommit( + commitSha: string, + date: string +): Promise { + // List of commits + const commitShaList = await getCommitListCached(); + + // List of QAF builds + const qafBuilds = await buildkite.getBuildsAfterDate(QA_FTR_TEST_SLUG, date, 30); + + // Find the first build that contains this commit + const build = qafBuilds.find((kbBuild) => { + // Check if build.commit is after commitSha? + const kibanaCommitSha = tryGetKibanaBuildHashFromQAFBuild(kbBuild); + const buildkiteBuildShaIndex = commitShaList.findIndex((c) => c.sha === kibanaCommitSha); + const commitShaIndex = commitShaList.findIndex((c) => c.sha === commitSha); + + return ( + commitShaIndex !== -1 && + buildkiteBuildShaIndex !== -1 && + buildkiteBuildShaIndex < commitShaIndex + ); + }); + + if (!build) { + return null; + } + + return { + success: build.state === 'passed', + stateEmoji: buildkiteBuildStateToEmoji(build.state), + url: build.web_url, + slug: QA_FTR_TEST_SLUG, + buildNumber: build.number, + commit: build.commit, + kibanaCommit: tryGetKibanaBuildHashFromQAFBuild(build), + startedAt: build.started_at, + finishedAt: build.finished_at, + }; +} +function tryGetKibanaBuildHashFromQAFBuild(build: Build) { + try { + const metaDataKeys = Object.keys(build.meta_data || {}); + const anyKibanaProjectKey = + metaDataKeys.find((key) => key.startsWith('project::bk-serverless')) || 'missing'; + const kibanaBuildInfo = JSON.parse(build.meta_data[anyKibanaProjectKey]); + return kibanaBuildInfo?.kibana_build_hash; + } catch (e) { + console.error(e); + return null; + } +} + +let _commitListCache: Array | null = null; +async function getCommitListCached() { + if (!_commitListCache) { + const resp = await octokit.request<'GET /repos/{owner}/{repo}/commits'>( + 'GET /repos/{owner}/{repo}/commits', + { + owner: 'elastic', + repo: 'kibana', + headers: { + accept: 'application/vnd.github.v3+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + } + ); + _commitListCache = resp.data; + } + return _commitListCache; +} + +function makeBuildInfoSnippetHtml(name: string, build: BuildkiteBuildExtract | null) { + if (!build) { + return `[❓] ${name} - no build found`; + } else { + const statedAt = build.startedAt + ? `started at ${new Date(build.startedAt).toUTCString()}` + : 'not started yet'; + const finishedAt = build.finishedAt + ? `finished at ${new Date(build.finishedAt).toUTCString()}` + : 'not finished yet'; + return `[${build.stateEmoji}] ${name} #${build.buildNumber} - ${statedAt}, ${finishedAt}`; + } +} + +export function makeBuildkiteBuildInfoHtml( + heading: string, + builds: Record +): string { + let html = `

${heading}

`; + for (const [name, build] of Object.entries(builds)) { + html += `
| ${makeBuildInfoSnippetHtml(name, build)}
\n`; + } + html += '
'; + + return html; +} + +export function makeCommitInfoWithBuildResultsHtml(commits: CommitWithStatuses[]) { + const commitWithBuildResultsHtml = commits.map((commitInfo) => { + const checks = commitInfo.checks; + const prBuildSnippet = makeBuildInfoSnippetHtml('on merge job', checks.onMergeBuild); + const ftrBuildSnippet = makeBuildInfoSnippetHtml('qaf/ftr tests', checks.ftrBuild); + const artifactBuildSnippet = makeBuildInfoSnippetHtml('artifact build', checks.artifactBuild); + const titleWithLink = commitInfo.title.replace( + /#(\d{4,6})/, + `$&` + ); + + return `
+
+ +
${titleWithLink} by ${commitInfo.author} on ${commitInfo.date}
+
| ${prBuildSnippet}
+
| ${artifactBuildSnippet}
+
| ${ftrBuildSnippet}
+
+
+
`; + }); + + return commitWithBuildResultsHtml.join('\n'); +} diff --git a/.buildkite/scripts/serverless/create_deploy_tag/info_sections/commit_info.ts b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/commit_info.ts new file mode 100644 index 0000000000000..31af2ec7f191b --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/commit_info.ts @@ -0,0 +1,115 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'; +import { buildkite, octokit, SELECTED_COMMIT_META_KEY, CURRENT_COMMIT_META_KEY } from '../shared'; + +export type GithubCommitType = RestEndpointMethodTypes['repos']['getCommit']['response']['data']; +export type ListedGithubCommitType = + RestEndpointMethodTypes['repos']['listCommits']['response']['data'][0]; + +const KIBANA_PR_BASE = 'https://github.com/elastic/kibana/pull'; + +export interface GitCommitExtract { + sha: string; + title: string; + message: string; + link: string; + date: string | undefined; + author: string | undefined; + prLink: string | undefined; +} + +export async function getCurrentQARelease() { + const releasesFile = await octokit.request(`GET /repos/{owner}/{repo}/contents/{path}`, { + owner: 'elastic', + repo: 'serverless-gitops', + path: 'services/kibana/versions.yaml', + }); + + // @ts-ignore + const fileContent = Buffer.from(releasesFile.data.content, 'base64').toString('utf8'); + + const sha = fileContent.match(`qa: "([a-z0-9]+)"`)?.[1]; + + if (!sha) { + throw new Error('Could not find QA hash in current releases file'); + } else { + buildkite.setMetadata(CURRENT_COMMIT_META_KEY, sha); + return sha; + } +} + +export function getSelectedCommitHash() { + const commitHash = buildkite.getMetadata(SELECTED_COMMIT_META_KEY); + if (!commitHash) { + throw new Error( + `Could not find selected commit (by '${SELECTED_COMMIT_META_KEY}' in buildkite meta-data)` + ); + } + return commitHash; +} + +export async function getCommitByHash(hash: string): Promise { + const commit = await octokit.repos.getCommit({ + owner: 'elastic', + repo: 'kibana', + ref: hash, + }); + + return commit.data; +} + +export async function getRecentCommits(commitCount: number): Promise { + const kibanaCommits: ListedGithubCommitType[] = ( + await octokit.repos.listCommits({ + owner: 'elastic', + repo: 'kibana', + per_page: Number(commitCount), + }) + ).data; + + return kibanaCommits.map(toGitCommitExtract); +} + +export function toGitCommitExtract( + commit: GithubCommitType | ListedGithubCommitType +): GitCommitExtract { + const title = commit.commit.message.split('\n')[0]; + const prNumber = title.match(/#(\d{4,6})/)?.[1]; + const prLink = prNumber ? `${KIBANA_PR_BASE}/${prNumber}` : undefined; + + return { + sha: commit.sha, + message: commit.commit.message, + title, + link: commit.html_url, + date: commit.commit.author?.date || commit.commit.committer?.date, + author: commit.author?.login || commit.committer?.login, + prLink, + }; +} + +export function makeCommitInfoHtml(sectionTitle: string, commitInfo: GitCommitExtract): string { + const titleWithLink = commitInfo.title.replace( + /#(\d{4,6})/, + `$&` + ); + + const commitDateUTC = new Date(commitInfo.date!).toUTCString(); + + return `
+

${sectionTitle}

+
+${commitInfo.sha} + by ${commitInfo.author} + on ${commitDateUTC} +
+
:merged-pr: ${titleWithLink}
+
`; +} diff --git a/.buildkite/scripts/serverless/create_deploy_tag/info_sections/so_snapshot_comparison.ts b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/so_snapshot_comparison.ts new file mode 100644 index 0000000000000..be937f49a46b9 --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/so_snapshot_comparison.ts @@ -0,0 +1,79 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import path from 'path'; +import { readFileSync } from 'fs'; +import { exec } from '../shared'; +import { BuildkiteClient, getKibanaDir } from '#pipeline-utils'; + +export function compareSOSnapshots( + previousSha: string, + selectedSha: string +): null | { + hasChanges: boolean; + changed: string[]; + command: string; +} { + assertValidSha(previousSha); + assertValidSha(selectedSha); + + const command = `node scripts/snapshot_plugin_types compare --from ${previousSha} --to ${selectedSha}`; + const outputPath = path.resolve(getKibanaDir(), 'so_comparison.json'); + + try { + exec(`${command} --outputPath ${outputPath}`, { stdio: 'inherit' }); + + const soComparisonResult = JSON.parse(readFileSync(outputPath).toString()); + + const buildkite = new BuildkiteClient({ exec }); + buildkite.uploadArtifacts(outputPath); + + return { + hasChanges: soComparisonResult.hasChanges, + changed: soComparisonResult.changed, + command, + }; + } catch (ex) { + console.error(ex); + return null; + } +} + +export function makeSOComparisonBlockHtml(comparisonResult: { + hasChanges: boolean; + changed: string[]; + command: string; +}): string { + if (comparisonResult.hasChanges) { + return `
+

Plugin Saved Object migration changes: *yes, ${comparisonResult.changed.length} plugin(s)*

+
Changed plugins: ${comparisonResult.changed.join(', ')}
+Find detailed info in the archived artifacts, or run the command yourself: +
${comparisonResult.command}
+
`; + } else { + return `
+

Plugin Saved Object migration changes: none

+No changes between targets, you can run the command yourself to verify: +
${comparisonResult.command}
+
`; + } +} + +export function makeSOComparisonErrorHtml(): string { + return `
+

Plugin Saved Object migration changes: N/A

+
Could not compare plugin migrations. Check the logs for more info.
+
`; +} + +function assertValidSha(sha: string) { + if (!sha.match(/^[a-f0-9]{8,40}$/)) { + throw new Error(`Invalid sha: ${sha}`); + } +} diff --git a/.buildkite/scripts/serverless/create_deploy_tag/info_sections/useful_links.ts b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/useful_links.ts new file mode 100644 index 0000000000000..c5c042f9ce0a1 --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/info_sections/useful_links.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +function link(text: string, url: string) { + return `${text}`; +} + +function getLinkForGPCTLNonProd(commit: string) { + return `https://overview.qa.cld.elstc.co/app/dashboards#/view/serverless-tooling-gpctl-deployment-status?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&service-name=kibana&_a=(controlGroupInput:(chainingSystem:HIERARCHICAL,controlStyle:oneLine,ignoreParentSettings:(ignoreFilters:!f,ignoreQuery:!f,ignoreTimerange:!f,ignoreValidations:!f),panels:('18201b8e-3aae-4459-947d-21e007b6a3a5':(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:commit-hash,id:'18201b8e-3aae-4459-947d-21e007b6a3a5',selectedOptions:!('${commit}'),title:commit-hash),grow:!t,order:1,type:optionsListControl,width:medium),'41060e65-ce4c-414e-b8cf-492ccb19245f':(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:service-name,id:'41060e65-ce4c-414e-b8cf-492ccb19245f',selectedOptions:!(kibana),title:service-name),grow:!t,order:0,type:optionsListControl,width:medium),ed96828e-efe9-43ad-be3f-0e04218f79af:(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:to-env,id:ed96828e-efe9-43ad-be3f-0e04218f79af,selectedOptions:!(qa),title:to-env),grow:!t,order:2,type:optionsListControl,width:medium))))`; +} + +function getLinkForGPCTLProd(commit: string) { + return `https://overview.elastic-cloud.com/app/dashboards#/view/serverless-tooling-gpctl-deployment-status?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&service-name=kibana&_a=(controlGroupInput:(chainingSystem:HIERARCHICAL,controlStyle:oneLine,ignoreParentSettings:(ignoreFilters:!f,ignoreQuery:!f,ignoreTimerange:!f,ignoreValidations:!f),panels:('18201b8e-3aae-4459-947d-21e007b6a3a5':(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:commit-hash,id:'18201b8e-3aae-4459-947d-21e007b6a3a5',selectedOptions:!('${commit}'),title:commit-hash),grow:!t,order:1,type:optionsListControl,width:medium),'41060e65-ce4c-414e-b8cf-492ccb19245f':(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:service-name,id:'41060e65-ce4c-414e-b8cf-492ccb19245f',selectedOptions:!(kibana),title:service-name),grow:!t,order:0,type:optionsListControl,width:medium),ed96828e-efe9-43ad-be3f-0e04218f79af:(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:to-env,id:ed96828e-efe9-43ad-be3f-0e04218f79af,selectedOptions:!(production),title:to-env),grow:!t,order:2,type:optionsListControl,width:medium))))`; +} + +export function getUsefulLinks({ + selectedCommitHash, + previousCommitHash, +}: { + previousCommitHash: string; + selectedCommitHash: string; +}): Record { + return { + 'Commits contained in deploy': `https://github.com/elastic/kibana/compare/${previousCommitHash}...${selectedCommitHash}`, + 'Argo Workflow (use Elastic Cloud Staging VPN)': `https://argo-workflows.cd.internal.qa.elastic.cloud/workflows?label=hash%3D${selectedCommitHash}`, + 'GPCTL Deployment Status dashboard for nonprod': getLinkForGPCTLNonProd(selectedCommitHash), + 'GPCTL Deployment Status dashboard for prod': getLinkForGPCTLProd(selectedCommitHash), + 'Quality Gate pipeline': `https://buildkite.com/elastic/kibana-tests/builds?branch=main`, + 'Kibana Serverless Release pipeline': `https://buildkite.com/elastic/kibana-serverless-release/builds?commit=${selectedCommitHash}`, + }; +} + +export function makeUsefulLinksHtml( + heading: string, + data: { + previousCommitHash: string; + selectedCommitHash: string; + } +) { + return ( + `

${heading}

` + + Object.entries(getUsefulLinks(data)) + .map(([name, url]) => `
:link: ${link(name, url)}
`) + .join('\n') + ); +} diff --git a/.buildkite/scripts/serverless/create_deploy_tag/list_commit_candidates.ts b/.buildkite/scripts/serverless/create_deploy_tag/list_commit_candidates.ts new file mode 100755 index 0000000000000..44d594f324f12 --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/list_commit_candidates.ts @@ -0,0 +1,111 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + buildkite, + COMMIT_INFO_CTX, + CommitWithStatuses, + exec, + SELECTED_COMMIT_META_KEY, +} from './shared'; +import { + getArtifactBuild, + getOnMergePRBuild, + getQAFBuildContainingCommit, + makeCommitInfoWithBuildResultsHtml, +} from './info_sections/build_info'; +import { getRecentCommits, GitCommitExtract } from './info_sections/commit_info'; +import { BuildkiteInputStep } from '#pipeline-utils'; + +async function main(commitCountArg: string) { + console.log('--- Listing commits'); + const commitCount = parseInt(commitCountArg, 10); + const commitData = await collectAvailableCommits(commitCount); + const commitsWithStatuses = await enrichWithStatuses(commitData); + + console.log('--- Updating buildkite context with listed commits'); + const commitListWithBuildResultsHtml = makeCommitInfoWithBuildResultsHtml(commitsWithStatuses); + exec(`buildkite-agent annotate --style 'info' --context '${COMMIT_INFO_CTX}'`, { + input: commitListWithBuildResultsHtml, + }); + + console.log('--- Generating buildkite input step'); + addBuildkiteInputStep(); +} + +async function collectAvailableCommits(commitCount: number): Promise { + console.log('--- Collecting recent kibana commits'); + + const recentCommits = await getRecentCommits(commitCount); + + if (!recentCommits) { + throw new Error('Could not find any, while listing recent commits'); + } + + return recentCommits; +} + +async function enrichWithStatuses(commits: GitCommitExtract[]): Promise { + console.log('--- Enriching with build statuses'); + + const commitsWithStatuses: CommitWithStatuses[] = await Promise.all( + commits.map(async (commit) => { + const onMergeBuild = await getOnMergePRBuild(commit.sha); + + if (!commit.date) { + return { + ...commit, + checks: { + onMergeBuild, + ftrBuild: null, + artifactBuild: null, + }, + }; + } + + const nextFTRBuild = await getQAFBuildContainingCommit(commit.sha, commit.date); + const artifactBuild = await getArtifactBuild(commit.sha); + + return { + ...commit, + checks: { + onMergeBuild, + ftrBuild: nextFTRBuild, + artifactBuild, + }, + }; + }) + ); + + return commitsWithStatuses; +} + +function addBuildkiteInputStep() { + const inputStep: BuildkiteInputStep = { + input: 'Select commit to deploy', + prompt: 'Select commit to deploy.', + key: 'select-commit', + fields: [ + { + text: 'Enter the release candidate commit SHA', + key: SELECTED_COMMIT_META_KEY, + }, + ], + }; + + buildkite.uploadSteps([inputStep]); +} + +main(process.argv[2]) + .then(() => { + console.log('Commit selector generated, added as a buildkite input step.'); + }) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/.buildkite/scripts/serverless/create_deploy_tag/mock_exec.ts b/.buildkite/scripts/serverless/create_deploy_tag/mock_exec.ts new file mode 100644 index 0000000000000..c3d4bbba61cd1 --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/mock_exec.ts @@ -0,0 +1,116 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * This file has a wrapper for exec, that stores answers for queries from a file, to be able to use it in tests. + */ + +import { execSync, ExecSyncOptions } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { getKibanaDir } from '#pipeline-utils'; + +const PREPARED_RESPONSES_PATH = + '.buildkite/scripts/serverless/create_deploy_tag/prepared_responses.json'; + +/** + * This module allows for a stand-in for execSync that stores calls, and responds from a file of recorded responses. + * Most of the components in this module are lazy, so that they are only initialized if needed. + * @param fake - if set to true, it will use the fake, prepared exec, if false, it will use child_process.execSync + * @param id - an optional ID, used to distinguish between different instances of exec. + */ +const getExec = (fake = false, id: string = randomId()) => { + return fake ? makeMockExec(id) : exec; +}; + +/** + * Lazy getter for a storage for calls to the mock exec. + */ +const getCallStorage: () => Record> = (() => { + let callStorage: Record> | null = null; + + return () => { + if (!callStorage) { + callStorage = new Proxy>>( + {}, + { + get: (target, prop: string) => { + if (!target[prop]) { + target[prop] = []; + } + return target[prop]; + }, + } + ); + } + return callStorage; + }; +})(); + +/** + * Lazy getter for the responses file. + */ +const loadFakeResponses = (() => { + let responses: any; + return () => { + if (!responses) { + const responsesFile = path.resolve(getKibanaDir(), PREPARED_RESPONSES_PATH); + if (fs.existsSync(responsesFile)) { + const responsesContent = fs.readFileSync(responsesFile).toString(); + responses = JSON.parse(responsesContent); + } else { + fs.writeFileSync(responsesFile, '{}'); + console.log(responsesFile, 'created'); + responses = {}; + } + } + + return responses; + }; +})(); + +const makeMockExec = (id: string) => { + console.warn("--- Using mock exec, don't use this on CI. ---"); + const callStorage = getCallStorage(); + const calls = callStorage[id]; + + const mockExecInstance = (command: string, opts: ExecSyncOptions = {}): string | null => { + const responses = loadFakeResponses(); + calls.push({ command, opts }); + + if (typeof responses[command] !== 'undefined') { + return responses[command]; + } else { + console.warn(`No response for command: ${command}`); + responses[command] = ''; + fs.writeFileSync( + path.resolve(getKibanaDir(), PREPARED_RESPONSES_PATH), + JSON.stringify(responses, null, 2) + ); + return exec(command, opts); + } + }; + + mockExecInstance.id = id; + mockExecInstance.calls = calls; + + return mockExecInstance; +}; + +const exec = (command: string, opts: any = {}) => { + const result = execSync(command, { encoding: 'utf-8', cwd: getKibanaDir(), ...opts }); + if (result) { + return result.toString().trim(); + } else { + return null; + } +}; + +const randomId = () => (Math.random() * 10e15).toString(36); + +export { getExec, getCallStorage }; diff --git a/.buildkite/scripts/serverless/create_deploy_tag/prepared_responses.json b/.buildkite/scripts/serverless/create_deploy_tag/prepared_responses.json new file mode 100644 index 0000000000000..046244851bffa --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/prepared_responses.json @@ -0,0 +1,13 @@ +{ + "buildkite-agent annotate --append --style 'info' --context 'commit-info'": "ok", + "buildkite-agent meta-data get \"commit-sha\"": "906987c2860b53b91d449bc164957857adddc06a", + "node scripts/snapshot_plugin_types compare --from b5aa37525578 --to 906987c2860b53b91d449bc164957857adddc06a --outputPath 'so_comparison.json'": "ok", + "buildkite-agent artifact upload 'so_comparison.json'": "ok", + "buildkite-agent meta-data get 'release_state'": "", + "buildkite-agent meta-data get 'state_data'": "", + "buildkite-agent meta-data set 'release_state'": "ok", + "buildkite-agent meta-data set 'state_data'": "ok", + "buildkite-agent annotate --context 'wizard-main' --style 'info'": "ok", + "buildkite-agent annotate --context 'wizard-instruction' --style 'info'": "ok", + "buildkite-agent annotate --context 'wizard-instruction' --style 'warning'": "ok" +} diff --git a/.buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts b/.buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts new file mode 100644 index 0000000000000..731f6720fd1ad --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/release_wizard_messaging.ts @@ -0,0 +1,372 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + buildkite, + COMMIT_INFO_CTX, + CURRENT_COMMIT_META_KEY, + DEPLOY_TAG_META_KEY, + octokit, + SELECTED_COMMIT_META_KEY, + sendSlackMessage, +} from './shared'; +import { GithubCommitType } from './info_sections/commit_info'; +import { getUsefulLinks } from './info_sections/useful_links'; + +const WIZARD_CTX_INSTRUCTION = 'wizard-instruction'; +const WIZARD_CTX_DEFAULT = 'wizard-main'; + +type StateNames = + | 'start' + | 'initialize' + | 'collect_commits' + | 'wait_for_selection' + | 'collect_commit_info' + | 'wait_for_confirmation' + | 'create_deploy_tag' + | 'tag_created' + | 'end' + | 'error_generic' + | string; + +interface StateShape { + name: string; + description: string; + instruction?: string; + instructionStyle?: 'success' | 'warning' | 'error' | 'info'; + display: boolean; + pre?: (state: StateShape) => Promise; + post?: (state: StateShape) => Promise; +} + +const states: Record = { + start: { + name: 'Starting state', + description: 'No description', + display: false, + post: async () => { + buildkite.setAnnotation(COMMIT_INFO_CTX, 'info', `

:kibana: Release candidates

`); + }, + }, + initialize: { + name: 'Initializing', + description: 'The job is starting up.', + instruction: 'Wait while we bootstrap. Follow the instructions displayed in this block.', + instructionStyle: 'info', + display: true, + }, + collect_commits: { + name: 'Collecting commits', + description: 'Collecting potential commits for the release.', + instruction: `Please wait, while we're collecting the list of available commits.`, + instructionStyle: 'info', + display: true, + }, + wait_for_selection: { + name: 'Waiting for selection', + description: 'Waiting for the Release Manager to select a release candidate commit.', + instruction: `Please find, copy and enter a commit SHA to the buildkite input box to proceed.`, + instructionStyle: 'warning', + display: true, + }, + collect_commit_info: { + name: 'Collecting commit info', + description: 'Collecting supplementary info about the selected commit.', + instruction: `Please wait, while we're collecting data about the commit, and the release candidate.`, + instructionStyle: 'info', + display: true, + pre: async () => { + buildkite.setAnnotation( + COMMIT_INFO_CTX, + 'info', + `

:kibana: Selected release candidate info:

` + ); + }, + }, + wait_for_confirmation: { + name: 'Waiting for confirmation', + description: 'Waiting for the Release Manager to confirm the release.', + instruction: `Please review the collected information above and unblock the release on Buildkite, if you're satisfied.`, + instructionStyle: 'warning', + display: true, + }, + create_deploy_tag: { + name: 'Creating deploy tag', + description: 'Creating the deploy tag, this will be picked up by another pipeline.', + instruction: `Please wait, while we're creating the deploy@timestamp tag.`, + instructionStyle: 'info', + display: true, + }, + tag_created: { + name: 'Release tag created', + description: 'The initial step release is completed, follow up jobs will be triggered soon.', + instruction: `

Deploy tag successfully created!

`, + post: async () => { + // The deployTag here is only for communication, if it's missing, it's not a big deal, but it's an error + const deployTag = + buildkite.getMetadata(DEPLOY_TAG_META_KEY) || + (console.error(`${DEPLOY_TAG_META_KEY} not found in buildkite meta-data`), 'unknown'); + const selectedCommit = buildkite.getMetadata(SELECTED_COMMIT_META_KEY); + const currentCommitSha = buildkite.getMetadata(CURRENT_COMMIT_META_KEY); + + buildkite.setAnnotation( + WIZARD_CTX_INSTRUCTION, + 'success', + `

Deploy tag successfully created!


+Your deployment will appear here on buildkite.` + ); + + if (!selectedCommit) { + // If we get here with no selected commit set, it's either an unsynced change in keys, or some weird error. + throw new Error( + `Couldn't find selected commit in buildkite meta-data (with key '${SELECTED_COMMIT_META_KEY}').` + ); + } + + const targetCommitData = ( + await octokit.repos.getCommit({ + owner: 'elastic', + repo: 'kibana', + ref: selectedCommit, + }) + ).data; + + await sendReleaseSlackAnnouncement({ + targetCommitData, + currentCommitSha, + deployTag, + }); + }, + instructionStyle: 'success', + display: true, + }, + end: { + name: 'End of the release process', + description: 'The release process has ended.', + display: false, + }, + error_generic: { + name: 'Encountered an error', + description: 'An error occurred during the release process.', + instruction: `

Please check the build logs for more information.

`, + instructionStyle: 'error', + display: false, + }, +}; + +/** + * This module is a central interface for updating the messaging interface for the wizard. + * It's implemented as a state machine that updates the wizard state as we transition between states. + * Use: `node /release_wizard_messaging.ts --state [--data ]` + */ +export async function main(args: string[]) { + if (!args.includes('--state')) { + throw new Error('Missing --state argument'); + } + const targetState = args.slice(args.indexOf('--state') + 1)[0]; + + let data: any; + if (args.includes('--data')) { + data = args.slice(args.indexOf('--data') + 1)[0]; + } + + const resultingTargetState = await transition(targetState, data); + if (resultingTargetState === 'tag_created') { + return await transition('end'); + } else { + return resultingTargetState; + } +} + +export async function transition(targetStateName: StateNames, data?: any) { + // use the buildkite agent to find what state we are in: + const currentStateName = buildkite.getMetadata('release_state') || 'start'; + const stateData = JSON.parse(buildkite.getMetadata('state_data') || '{}'); + + if (!currentStateName) { + throw new Error('Could not find current state in buildkite meta-data'); + } + + // find the index of the current state in the core flow + const currentStateIndex = Object.keys(states).indexOf(currentStateName); + const targetStateIndex = Object.keys(states).indexOf(targetStateName); + + if (currentStateIndex === -1) { + throw new Error(`Could not find current state '${currentStateName}' in core flow`); + } + const currentState = states[currentStateName]; + + if (targetStateIndex === -1) { + throw new Error(`Could not find target state '${targetStateName}' in core flow`); + } + const targetState = states[targetStateName]; + + if (currentStateIndex + 1 !== targetStateIndex) { + await tryCall(currentState.post, stateData); + stateData[currentStateName] = 'nok'; + } else { + const result = await tryCall(currentState.post, stateData); + stateData[currentStateName] = result ? 'ok' : 'nok'; + } + stateData[targetStateName] = 'pending'; + + await tryCall(targetState.pre, stateData); + + buildkite.setMetadata('release_state', targetStateName); + buildkite.setMetadata('state_data', JSON.stringify(stateData)); + + updateWizardState(stateData); + updateWizardInstruction(targetStateName, stateData); + + return targetStateName; +} + +function updateWizardState(stateData: Record) { + const wizardHeader = `

:kibana: Kibana Serverless deployment wizard :mage:

`; + + const wizardSteps = Object.keys(states) + .filter((stateName) => states[stateName].display) + .map((stateName) => { + const stateInfo = states[stateName]; + const stateStatus = stateData[stateName]; + const stateEmoji = { + ok: ':white_check_mark:', + nok: ':x:', + pending: ':hourglass_flowing_sand:', + missing: ':white_circle:', + }[stateStatus || 'missing']; + + if (stateStatus === 'pending') { + return `
[${stateEmoji}] ${stateInfo.name}
  - ${stateInfo.description}
`; + } else { + return `
[${stateEmoji}] ${stateInfo.name}
`; + } + }); + + const wizardHtml = `
+${wizardHeader} +${wizardSteps.join('\n')} +
`; + + buildkite.setAnnotation(WIZARD_CTX_DEFAULT, 'info', wizardHtml); +} + +function updateWizardInstruction(targetState: string, stateData: any) { + const { instructionStyle, instruction } = states[targetState]; + + if (instruction) { + buildkite.setAnnotation( + WIZARD_CTX_INSTRUCTION, + instructionStyle || 'info', + `${instruction}` + ); + } +} + +async function tryCall(fn: any, ...args: any[]) { + if (typeof fn === 'function') { + try { + const result = await fn(...args); + return result !== false; + } catch (error) { + console.error(error); + return false; + } + } else { + return true; + } +} + +async function sendReleaseSlackAnnouncement({ + targetCommitData, + currentCommitSha, + deployTag, +}: { + targetCommitData: GithubCommitType; + currentCommitSha: string | undefined | null; + deployTag: string; +}) { + const textBlock = (...str: string[]) => ({ type: 'mrkdwn', text: str.join('\n') }); + const buildShortname = `kibana-serverless-release #${process.env.BUILDKITE_BUILD_NUMBER}`; + + const isDryRun = process.env.DRY_RUN?.match('(1|true)'); + const mergedAtDate = targetCommitData.commit?.committer?.date; + const mergedAtUtcString = mergedAtDate ? new Date(mergedAtDate).toUTCString() : 'unknown'; + const targetCommitSha = targetCommitData.sha; + const targetCommitShort = targetCommitSha.slice(0, 12); + const compareResponse = ( + await octokit.repos.compareCommits({ + owner: 'elastic', + repo: 'kibana', + base: currentCommitSha || 'main', + head: targetCommitSha, + }) + ).data; + const compareLink = currentCommitSha + ? `<${compareResponse.html_url}|${compareResponse.total_commits} new commits>` + : 'a new release candidate'; + + const mainMessage = [ + `:ship_it_parrot: Promotion of ${compareLink} to QA has been <${process.env.BUILDKITE_BUILD_URL}|initiated>!\n`, + `*Remember:* Promotion to Staging is currently a manual process and will proceed once the build is signed off in QA.\n`, + ]; + if (isDryRun) { + mainMessage.unshift( + `*:memo:This is a dry run - no commit will actually be promoted. Please ignore!*\n` + ); + } else { + mainMessage.push(`cc: @kibana-serverless-promotion-notify`); + } + + const linksSection = { + 'Initiated by': process.env.BUILDKITE_BUILD_CREATOR || 'unknown', + 'Pre-release job': `<${process.env.BUILDKITE_BUILD_URL}|${buildShortname}>`, + 'Git tag': ``, + Commit: ``, + 'Merged at': mergedAtUtcString, + }; + + const usefulLinksSection = getUsefulLinks({ + previousCommitHash: currentCommitSha || 'main', + selectedCommitHash: targetCommitSha, + }); + + return sendSlackMessage({ + blocks: [ + { + type: 'section', + text: textBlock(...mainMessage), + }, + { + type: 'section', + fields: Object.entries(linksSection).map(([name, link]) => textBlock(`*${name}*:`, link)), + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: + '*Useful links:*\n\n' + + Object.entries(usefulLinksSection) + .map(([name, link]) => ` • <${link}|${name}>`) + .join('\n'), + }, + }, + ], + }); +} + +main(process.argv.slice(2)).then( + (targetState) => { + console.log('Transition completed to: ' + targetState); + }, + (error) => { + console.error(error); + process.exit(1); + } +); diff --git a/.buildkite/scripts/serverless/create_deploy_tag/shared.ts b/.buildkite/scripts/serverless/create_deploy_tag/shared.ts new file mode 100644 index 0000000000000..1d2ca817c5a72 --- /dev/null +++ b/.buildkite/scripts/serverless/create_deploy_tag/shared.ts @@ -0,0 +1,89 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import axios from 'axios'; + +import { getExec } from './mock_exec'; +import { GitCommitExtract } from './info_sections/commit_info'; +import { BuildkiteBuildExtract } from './info_sections/build_info'; +import { BuildkiteClient, getGithubClient } from '#pipeline-utils'; + +const SELECTED_COMMIT_META_KEY = 'selected-commit-hash'; +const CURRENT_COMMIT_META_KEY = 'current-commit-hash'; + +const DEPLOY_TAG_META_KEY = 'deploy-tag'; +const COMMIT_INFO_CTX = 'commit-info'; + +const octokit = getGithubClient(); + +const exec = getExec(!process.env.CI); + +const buildkite = new BuildkiteClient({ exec }); + +const buildkiteBuildStateToEmoji = (state: string) => { + return ( + { + running: '⏳', + scheduled: '⏳', + passed: '✅', + failed: '❌', + blocked: '❌', + canceled: '❌', + canceling: '❌', + skipped: '❌', + not_run: '❌', + finished: '✅', + }[state] || '❓' + ); +}; + +export { + octokit, + exec, + buildkite, + buildkiteBuildStateToEmoji, + SELECTED_COMMIT_META_KEY, + COMMIT_INFO_CTX, + DEPLOY_TAG_META_KEY, + CURRENT_COMMIT_META_KEY, +}; + +export interface CommitWithStatuses extends GitCommitExtract { + title: string; + author: string | undefined; + checks: { + onMergeBuild: BuildkiteBuildExtract | null; + ftrBuild: BuildkiteBuildExtract | null; + artifactBuild: BuildkiteBuildExtract | null; + }; +} + +export function sendSlackMessage(payload: any) { + if (!process.env.DEPLOY_TAGGER_SLACK_WEBHOOK_URL) { + console.log('No SLACK_WEBHOOK_URL set, not sending slack message'); + return Promise.resolve(); + } else { + return axios + .post( + process.env.DEPLOY_TAGGER_SLACK_WEBHOOK_URL, + typeof payload === 'string' ? payload : JSON.stringify(payload) + ) + .catch((error) => { + if (axios.isAxiosError(error) && error.response) { + console.error( + "Couldn't send slack message.", + error.response.status, + error.response.statusText, + error.message + ); + } else { + console.error("Couldn't send slack message.", error.message); + } + }); + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0e8b1e3f68b04..3f658a74d4948 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -555,6 +555,7 @@ x-pack/plugins/observability_ai_assistant @elastic/obs-knowledge-team x-pack/packages/observability/alert_details @elastic/obs-ux-management-team x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops +x-pack/packages/observability/get_padded_alert_time_range_util @elastic/obs-ux-management-team x-pack/plugins/observability_log_explorer @elastic/obs-ux-logs-team x-pack/plugins/observability_onboarding @elastic/obs-ux-logs-team x-pack/plugins/observability @elastic/obs-ux-management-team @@ -1266,7 +1267,6 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ ## Security Solution sub teams - Threat Hunting Explore /x-pack/plugins/security_solution/common/api/tags @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/common/api/risk_score @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/common/search_strategy/security_solution/network @elastic/security-threat-hunting-explore @@ -1377,7 +1377,6 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/public/detection_engine/rule_exceptions @elastic/security-detection-engine /x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists @elastic/security-detection-engine /x-pack/plugins/security_solution/public/detections/pages/alerts @elastic/security-detection-engine -/x-pack/plugins/security_solution/public/entity_analytics @elastic/security-detection-engine /x-pack/plugins/security_solution/public/exceptions @elastic/security-detection-engine /x-pack/plugins/security_solution/server/lib/detection_engine/migrations @elastic/security-detection-engine @@ -1395,7 +1394,6 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_edit @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists @elastic/security-detection-engine -/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/exceptions @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/overview @elastic/security-detection-engine @@ -1483,6 +1481,9 @@ x-pack/plugins/security_solution/server/lib/entity_analytics @elastic/security-e x-pack/plugins/security_solution/server/lib/risk_score @elastic/security-entity-analytics x-pack/test/security_solution_api_integration/test_suites/entity_analytics @elastic/security-entity-analytics x-pack/plugins/security_solution/public/flyout/entity_details @elastic/security-entity-analytics +x-pack/plugins/security_solution/common/api/entity_analytics @elastic/security-entity-analytics +/x-pack/plugins/security_solution/public/entity_analytics @elastic/security-entity-analytics +/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics @elastic/security-entity-analytics # Security Defend Workflows - OSQuery Ownership /x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions @elastic/security-defend-workflows diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 68916c4e28216..f138c1c5c359d 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index def992d9cd6cb..a2051ac2c8bef 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 21af623fb3958..4a42aea901502 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 50496a9c06dee..4d77f09b25730 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 920537f06ea0b..466ea7d3f5b00 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index b7415d4dd8001..d1ab576db466d 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 8061a2ca975db..953d8671ac817 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 558cc4ae30a7a..28d093c11023e 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index c70d15601728d..6580f9ea76dab 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 31f503aec9183..d457f409b45f4 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 1c592bbe9df92..0a0841637a929 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index dd8c395df407b..904a5062dac32 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 47ef98168188c..de889e93897dd 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 012af0acb1598..b947f80442143 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 5f636b420311f..213ce0d815f74 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 6e3216c615832..eea50850337aa 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index e01df7b3719a7..acbeed09a14a5 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 8f1f19c74f6f7..ee25c9cd2435d 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 66f0570f9a083..78d671036d4c1 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 35febfe958f6e..acdf81d17bd31 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 59c19987d604d..ec0c1cdb41b64 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 5731fb8896f99..7891e3265e6f4 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index f28f84acfb43d..5f656d7e6ca64 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index 7ddcca4750bf7..3e25d1dccdc9a 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13241,7 +13241,7 @@ "\nReturns scripted fields" ], "signature": [ - "() => { storedFields: string[]; scriptFields: Record { scriptFields: Record; docvalueFields: { field: string; format: string; }[]; runtimeFields: ", "MappingRuntimeFields", @@ -19402,7 +19402,7 @@ "\nReturns scripted fields" ], "signature": [ - "() => { storedFields: string[]; scriptFields: Record { scriptFields: Record; docvalueFields: { field: string; format: string; }[]; runtimeFields: ", "MappingRuntimeFields", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 82e38cd78f2f5..115faccb42931 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 463928ab4e04e..ad56714e20e0a 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 2129f27785292..bd4816f55eb08 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 1b57b19f9be14..5c5d9ef9bcb13 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index e9f0bd6c06dc6..5bf963488e651 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 144c1dd9c3e7b..11ed98f63ddbb 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 96792d381cdf7..1f7e5f674a006 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -168,7 +168,7 @@ "\nReturns scripted fields" ], "signature": [ - "() => { storedFields: string[]; scriptFields: Record { scriptFields: Record; docvalueFields: { field: string; format: string; }[]; runtimeFields: ", "MappingRuntimeFields", @@ -6573,7 +6573,7 @@ "\nReturns scripted fields" ], "signature": [ - "() => { storedFields: string[]; scriptFields: Record { scriptFields: Record; docvalueFields: { field: string; format: string; }[]; runtimeFields: ", "MappingRuntimeFields", @@ -12015,7 +12015,7 @@ "\nReturns scripted fields" ], "signature": [ - "() => { storedFields: string[]; scriptFields: Record { scriptFields: Record; docvalueFields: { field: string; format: string; }[]; runtimeFields: ", "MappingRuntimeFields", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index f423e382fb519..a537515057d8f 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 86ad4a73b9d0a..17a63d00d8d5d 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index bd531645c5a47..b3d7806f25448 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index d9ef62cbd84cc..04b7b5742a066 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 7a03beaa58b70..db77800813a5b 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 5733d3a301935..80601d39cc683 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 606e30fcb7bbb..247a452db7358 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 2b6a9b1e2eb17..173cd3b438213 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 6b33b2e6c94ca..31034e5bd04e4 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 15e4ab2986274..71a85c6430b3f 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 244f976ce9ad7..f1c6031b89c45 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 8b1a1e4e3568d..91470b99e83df 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 056c6d15173b9..28516da0e6fd6 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 4e58bcd454f3c..85684499a4235 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index a77a21f6614b9..d644a01b3c500 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 3dfac3f762d0e..08c118c00e49b 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 5313045188d0d..dffcf4a6839e5 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index 1f75f1401d217..295bc387249fe 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index cfb0ffc9e1dc4..572b3a8288082 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 6963137fb77a9..c029f28ca9703 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 2f5dad63bc0fd..d827d28b00aad 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index fd706ce1e71a1..56bbf70d3a039 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 6bab5564f1e4c..0fe970b94a7a1 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 10c07dd4f8d84..19146305554d6 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index fc73b804ff2b6..dff5d482cd0f2 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index a51cccb44559f..1c6c006b61c45 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index d92e882c05e62..cbadff9f5370a 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index ce09249de1fa3..b5ffc6baf5be3 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 0532fa0793b75..a02d0beb7ad60 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 4ff918016c804..7695cb73ba832 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index a71cc14f67571..c70d1b3d0e170 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index d4f2e2a44a391..43c4df5bba00a 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index dd7908b619ba2..bd947c97d631a 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index ea4b579872f32..df6633e14f5c3 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 68e30f9962b27..e99826aa358b3 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index a73bd48e1094f..0e101636117db 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 397cf2b9a6074..f8f2eb2a60bb0 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 70db8231e2c9e..c95b449738a30 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 130f51934ddb6..8ed774fdd65dc 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 6a3f7491c81a9..986c9db26d9a0 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index fff05062156e8..8b592582da5ae 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 5698163c47c81..5f338a72f4f77 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 3d8eeeb6e97f2..6289781823e44 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index e3c023ca167cf..fbac6a747e7a6 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index d462edb008e00..88203ba96a5b8 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index bc91cf4e0e432..a658bf5b280cf 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index e587731395ffa..c202311da5b27 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 268f238a50baa..7c708281c4fb9 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 669976b59f5ab..090115e69355f 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 7f90ed50cdae4..2bbf8f57cc372 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index daa559388ed16..89b2cf714c363 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index b76cb84554864..c54e86a5cecbd 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index 05ca9737bb8fa..cac09ea0812ee 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 85da58fb6dfae..7fddb0e09932d 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 5b017db7bac95..681ee004ba5e3 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 296e017b07595..fb3cdec0d4ad2 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 51acb112dd9cf..715d59a2bc561 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 1033ee3d89053..add6f05c8c657 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 5a5034563ee84..18363b0c5160c 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 7a359de1e2c77..aadc13a75245f 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 838e043df0069..be2b890199ddc 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 48b39e1d90242..92c649f186b57 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 3cc052bc1d843..1006c7fcbf5f1 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 7389fba61e6bb..97ba04dd8361a 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 605b31344c1fe..794f53febea8f 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 3aa5c4acf9e8e..c5cdf3ad798f0 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index c84fecba9bc20..0eabdff37e6f6 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 65edd82ebe63d..e697ff4cbb7f3 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index b8259d55e9070..b3c9973a88435 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index dd64eccf7819a..73c5a8b2ed02b 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index 937284372211f..fa7d91c7df9a3 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 46745458f4d20..b8a2f2ab9f8d7 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index e275c63dda2c9..6a042cff71c07 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index 5bbb5f0aa435f..7a4858764b5ac 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 6e927b0979253..34736725d6b02 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index ca606c1bcdf72..e8407cfdbc17b 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 0c95285d554ed..f4cdb52acf2ce 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 23c39200b6296..e19f46ec65799 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 0d1099bc37f77..b55aac476b683 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 32008ffdc3426..4f2970065b4e8 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 5484c8e7d2abb..96901a5372797 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index bb353457cdbd5..9a7497408d58b 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index dec17f3438132..ad0291b194e94 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 98fd31c288051..fae9f9575bf17 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 2fc2b20c02c93..86b0c6571936c 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index 8d5ba4de2b0e1..8c02d52f1be5c 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index b451e404868fb..ee81d65c7cfb2 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index e1b8dd058a43f..fee42b9fbd317 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 0025128a2fb71..7215f60ca9204 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index fd77c783499df..627d1bc4bab0b 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 5913d0ceac884..ace064b5df35b 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index d5c3b1004a05a..2fd6adbf94b49 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 05da627bc9ba7..4f8f31eba6a12 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index e7ed5fbae36fb..039bf8a28bbfa 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index cfe499e7b00d6..5f9aa7b279c4a 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index fbf420bfa3f58..cfb68ad81b52d 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 95f857ccb6e98..830451de66fe1 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 3c15e355b4b37..34b132d0231d5 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 9a13b1a3a99b9..28aac5e0ffe51 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index c2bef93d1f3f5..3c50f52d8569f 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index fbc5a26540942..984225ee1995c 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 6a5ff364e6063..4c755f0cc1ca7 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 5ab42ff2bc81e..4d100ff96a81e 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 2d54a1f0b6c1d..928d29454ba78 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index f312e32f8029c..eb146cc622db2 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 2f8820e35c7f1..0d9a0a016ed23 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 262ae1955ee1c..414a8232a871c 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 64e717febd145..96716e4ab8335 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 493cc13f487c3..46a3d4f542df3 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 33ae90c72bbef..135a9d69a1e16 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 316c19d217149..80f8f79434ed2 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 156d4dd5740db..92bac398a50b2 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index d3f3f05fdb6f5..29e7246a5245f 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 9e510ca0c5c72..46fec3c78676a 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index 1fd77880a135d..d231e576b2cdf 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index a9996954d1dd8..46a07f3b3b64d 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index 78b3f63fd491a..437ba3316a1e4 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index de78e4c889cc4..9501ed1123e31 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 1068470504b3b..5c210ba86816b 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index daa23595e61e8..7d840030e4bb9 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index d94d5a2f90afa..6b05c85708db4 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index c92afb612e907..da2565ed13592 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 7959bd19f7d41..033be2051e3fb 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 7d6cb43d509f2..a74c455b54e0d 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 1ae964ab2afc1..b4f079d7c0a06 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 4956dc44fc716..37cbdd4b641cd 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 1b4f0a33316c9..d390bf2b032ea 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index cc4a5ec79f326..a77b06d3767d6 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 1b5be4dd4ee66..c10746bfb8dcd 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 66473b0f70fd6..240c54239fa44 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 98960fe94d8f0..74997267bde2b 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index f01b4cf1b858c..8d5d5eddfd47b 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 933882793dd57..255d435ad8748 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 8d83caf6ed83b..229fccc3cd9de 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 48bfc5390b611..3cb7e57dfd641 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index c0f8322de528b..87fdfe47a6663 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 5304cbf7a4ff6..3767dad87c8fa 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 7f8b429bf80d7..61d363a791fa3 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index c6a8478777393..4412f77e8565a 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index ace0c742419a3..f6d8d07a50953 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 1e16c1e93fd14..cc0bb4d15b7eb 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 784271a07e1d9..27bae896718ad 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 08de9500ac660..cdb226d1179a3 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 8b1ee90f6e2e9..2c4e9cf89b5a3 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index ecd79ee1acf08..70d6196b734c1 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 2d787309d6525..4fe4a1184d5e8 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 99eb3713657a5..e0ae793051cf0 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 97c330ff643fd..444a179c0d7a6 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index d6c8f675d51e5..59daf561c6e27 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 78fe8f2170898..2493a32785684 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 8862ae99d5d79..3e3e28d517b7f 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 82d168ff2791b..8dc9fb56c5ce3 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index 5f2161e825cf7..12054479077cd 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 870a963e08a7d..fffa2c007a4af 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 5a5021f6fea2a..5f4c1b81e41d7 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 7f35207ea3ba1..129a370d54dbd 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 7887cb4805b84..edb840a1d7257 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index a459d76ca6828..4c1470db2c148 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 9dd44697fe241..0c8026c4be8c6 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -13611,47 +13611,47 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts" + "path": "x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts" + "path": "x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts" }, { "plugin": "securitySolution", @@ -14875,14 +14875,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts" @@ -14923,6 +14915,18 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts" @@ -15415,15 +15419,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts" + "path": "x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts" }, { "plugin": "synthetics", diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index b6eb0fe24c2b6..710dc702050dd 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 11decde852e01..05d14a8d5b846 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 9fd169f4050e1..cb9d09104bd21 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index d82cc5a4455de..514cc99f3fbe6 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 5fdb9ab1b017b..154890a256bfe 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 590b532fdd289..7ce3f78a34cbe 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 1beb63f3e586e..51a9b7680c107 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index b9fd0f085f42a..2e6dea46d45be 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 4ed6860b53223..a50becb62111c 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index c0c8d3605225f..e2d76bd64cad1 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index d63e6c38298c3..47916591f71de 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index ec79d1e75adf0..4489e8f11e0d1 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index e92e325c7c126..382f8ceababe8 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index aec5407e06805..666923335b16a 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 179401d51bd02..dd7bd7e2b7bb7 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index a1f38d78aa106..2e1c1b1cd8788 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 52709de895f51..a93e985a42d2c 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 9498b18e2bba2..cb1d8cec385fe 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 707a3348452b3..0b280809cc06e 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index faa59dfee3528..9a7947745b675 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 52fa16455f0d0..14f7d376c4679 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index ee533128d7eac..07ed5dd9c5bbd 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.devdocs.json b/api_docs/kbn_core_metrics_server.devdocs.json index edbaa1a24d199..1d18b5552b80b 100644 --- a/api_docs/kbn_core_metrics_server.devdocs.json +++ b/api_docs/kbn_core_metrics_server.devdocs.json @@ -705,6 +705,22 @@ "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-common.OpsOsMetrics.cgroup_memory", + "type": "Object", + "tags": [], + "label": "cgroup_memory", + "description": [ + "memory cgroup metrics, undefined when not running in cgroup v2" + ], + "signature": [ + "{ current_in_bytes: number; swap_current_in_bytes: number; } | undefined" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -745,7 +761,7 @@ "process memory usage" ], "signature": [ - "{ heap: { total_in_bytes: number; used_in_bytes: number; size_limit: number; }; resident_set_size_in_bytes: number; }" + "{ heap: { total_in_bytes: number; used_in_bytes: number; size_limit: number; }; resident_set_size_in_bytes: number; external_in_bytes: number; array_buffers_in_bytes: number; }" ], "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", "deprecated": false, diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 192e02612f6c9..712d7a4a3a977 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 55 | 0 | 8 | 0 | +| 56 | 0 | 8 | 0 | ## Common diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 3e44eb52ac0e2..09c9bfd8e015f 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 9fe08c2c9f318..fcc9748e1e7f1 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 2b7e828396289..f08f2fbf8f38f 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 9713e7142176c..2d2eb77f82d96 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index a394db59653f6..e102321639578 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index e65611376db50..8db009d8daecd 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index c28c518e2ade3..2be62fa42919f 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 76c24d8d8140d..1b33f6d1b3bdc 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index f85a294166d0e..6be1ad4c41d10 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 91ba5fbb3c59c..5ce6cc21680fb 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index daf7a1a4a66fa..3ce6e3a6b3234 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 774f857a16301..16a2489e6d921 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 35e68259281dd..126f3a8ee0869 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index ceb56808dcb59..6c5e4335e156e 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index 7fc2a392370ba..dd72ca0418768 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index 49da04677f9a3..ce9d24dc56b7a 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 405471b44455f..6e675cffb93bc 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index c428ae43e7ea2..215b9caf291d1 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index c9b2e1679c041..5f1dc05d75c9f 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index bfcd315fc8954..8ec8d55a92965 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 943e8a1c780a6..00fcc2c96ad47 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index f8ff4563959fb..42c8360499830 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 70c48fe2acf2a..eda279fa3a8bb 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 38fa6388ca84c..2999ace9b7126 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index d6801271c14b5..6e33e2dffd714 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 658177adebb58..6b07ce153a68e 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 1c24cca7ea9f1..b35fea47c9641 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json b/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json index bfdf4b5b8d482..ff317f16d9a6c 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json @@ -652,6 +652,99 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getFieldListFromTypeMapping", + "type": "Function", + "tags": [], + "label": "getFieldListFromTypeMapping", + "description": [ + "\nReturn the list of fields present in the provided mappings.\nNote that fields only containing properties are still considered fields by this function.\n" + ], + "signature": [ + "(typeMappings: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsTypeMappingDefinition", + "text": "SavedObjectsTypeMappingDefinition" + }, + ") => string[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_field_list.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getFieldListFromTypeMapping.$1", + "type": "Object", + "tags": [], + "label": "typeMappings", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsTypeMappingDefinition", + "text": "SavedObjectsTypeMappingDefinition" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_field_list.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getFieldListMapFromMappingDefinitions", + "type": "Function", + "tags": [], + "label": "getFieldListMapFromMappingDefinitions", + "description": [ + "\nReturn the list of fields present in each individual type mappings present in the definition." + ], + "signature": [ + "(mappings: ", + "SavedObjectsTypeMappingDefinitions", + ") => ", + { + "pluginId": "@kbn/core-saved-objects-base-server-internal", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsBaseServerInternalPluginApi", + "section": "def-common.FieldListMap", + "text": "FieldListMap" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_field_list.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getFieldListMapFromMappingDefinitions.$1", + "type": "Object", + "tags": [], + "label": "mappings", + "description": [], + "signature": [ + "SavedObjectsTypeMappingDefinitions" + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_field_list.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-base-server-internal", "id": "def-common.getIndexForType", @@ -1072,6 +1165,111 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getVersionAddedFields", + "type": "Function", + "tags": [], + "label": "getVersionAddedFields", + "description": [ + "\nReturn the list of fields, sorted, that were introduced in the given version." + ], + "signature": [ + "(version: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsModelVersion", + "text": "SavedObjectsModelVersion" + }, + ") => string[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/model_version/version_mapping_changes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getVersionAddedFields.$1", + "type": "Object", + "tags": [], + "label": "version", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsModelVersion", + "text": "SavedObjectsModelVersion" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/model_version/version_mapping_changes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getVersionAddedMappings", + "type": "Function", + "tags": [], + "label": "getVersionAddedMappings", + "description": [ + "\nReturn the mappings that were introduced in the given version.\nIf multiple 'mappings_addition' changes are present for the version,\nthey will be deep-merged." + ], + "signature": [ + "(version: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsModelVersion", + "text": "SavedObjectsModelVersion" + }, + ") => ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsMappingProperties", + "text": "SavedObjectsMappingProperties" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/model_version/version_mapping_changes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.getVersionAddedMappings.$1", + "type": "Object", + "tags": [], + "label": "version", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsModelVersion", + "text": "SavedObjectsModelVersion" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/model_version/version_mapping_changes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-base-server-internal", "id": "def-common.getVirtualVersionMap", @@ -1512,6 +1710,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-base-server-internal", + "id": "def-common.FieldListMap", + "type": "Type", + "tags": [], + "label": "FieldListMap", + "description": [], + "signature": [ + "{ [x: string]: string[]; }" + ], + "path": "packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_field_list.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-base-server-internal", "id": "def-common.globalSwitchToModelVersionAt", diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 196cb5f47138b..18e2abda1841f 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 89 | 0 | 61 | 10 | +| 98 | 0 | 66 | 10 | ## Common diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 19b86390bb057..cd60f6974d43d 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 65c2b66236398..8ea9db777387c 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index cf8f97220f7c3..6838f4bc662cd 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 528cdf99bf2c8..eab473f55a993 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 560b7ffb9413f..a4c22f5830273 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index a93e665b668e7..ddf2d0d43d49c 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index ae316087c45b3..d03d0ed886ca7 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index aca9b4e650bcb..a3e663abb314f 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 4c331a7b25f56..e55d076db5063 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index b04287ece28dc..bd884b678e1c0 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 4019d3550bda0..0e246eced57a5 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 0df6190b445b6..903bd26b11c11 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 8e72ffea83ac0..9474f27826a39 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 8f63af040222c..90cf71c04eadf 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index daf9b0725da59..6a982ccce1afb 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 24bd9139388ef..912a67ee4a53e 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 2f2cc86c123e2..50e513d68bc1c 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 8fd3ef3d400f7..169563847ec27 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 235ad6bccb286..4c3acb4faf34d 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 3bbd0c1a8494a..d12cd17a6fcec 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index a0cfc53b59cd3..2e15f58d40029 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index bebe63743796a..f5e84c23428f3 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 615b72021f677..ff36a80d6beaa 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index e4d6b7039b524..71545da9c2519 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 50f2d5bbbfaa7..a2483cf3dac28 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 807f84e835f67..4e601a17239d5 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index b31742792a185..9de226733801c 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 6f9456f9d0811..3e15463830b4e 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 72c4f15f5dfd3..1990f304de1f2 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 3a29453af2bcb..ae81e4005826a 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index e48aad8225859..fb23e0b34653f 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 8ee5fd6fb1e63..3af92aea551be 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index cae0028dd414e..dd1c80b3469ba 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index e85cede9f22a1..992249185e6e2 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 61c041450589f..d481db940f371 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 4a15daa6dc964..bcaad231a0777 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 8d33710cc9445..c1fb7e1f4d4ff 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_internal.mdx b/api_docs/kbn_core_user_settings_server_internal.mdx index e30818e2e532e..6876137198798 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-internal'] --- import kbnCoreUserSettingsServerInternalObj from './kbn_core_user_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index a94d85c11414f..09085928aa694 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 06c0c6557bb7e..ab2edffc0416e 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 327f4a1453f7c..b1a0bd6a6d6c6 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index 289f04197a848..7b853f86742a0 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 917d2e2d3b118..dd5b4b235110e 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 3f3764304e644..36c8d59212257 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 3884f08923a99..53b56e6a88e03 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index c20d177d4b1c1..d11ad8132b1e0 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 3b56f6adca9f0..00f7f68e10e43 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index d9e9d8d1eb42d..778d020e6c998 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 44e2e8fcd1930..754381505010d 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.devdocs.json b/api_docs/kbn_deeplinks_ml.devdocs.json index 8ba46143a88f3..15449e28dcf5a 100644 --- a/api_docs/kbn_deeplinks_ml.devdocs.json +++ b/api_docs/kbn_deeplinks_ml.devdocs.json @@ -45,7 +45,7 @@ "label": "DeepLinkId", "description": [], "signature": [ - "\"ml\" | \"ml:nodes\" | \"ml:notifications\" | \"ml:overview\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:singleMetricViewer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:logRateAnalysis\" | \"ml:logPatternAnalysis\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:memoryUsage\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\"" + "\"ml\" | \"ml:nodes\" | \"ml:notifications\" | \"ml:overview\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:memoryUsage\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:singleMetricViewer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:logRateAnalysis\" | \"ml:logPatternAnalysis\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\"" ], "path": "packages/deeplinks/ml/deep_links.ts", "deprecated": false, @@ -60,7 +60,7 @@ "label": "LinkId", "description": [], "signature": [ - "\"nodes\" | \"notifications\" | \"overview\" | \"settings\" | \"dataVisualizer\" | \"anomalyDetection\" | \"anomalyExplorer\" | \"singleMetricViewer\" | \"dataDrift\" | \"dataFrameAnalytics\" | \"resultExplorer\" | \"analyticsMap\" | \"aiOps\" | \"logRateAnalysis\" | \"logPatternAnalysis\" | \"changePointDetections\" | \"modelManagement\" | \"nodesOverview\" | \"memoryUsage\" | \"fileUpload\" | \"indexDataVisualizer\" | \"calendarSettings\" | \"filterListsSettings\"" + "\"nodes\" | \"notifications\" | \"overview\" | \"settings\" | \"dataVisualizer\" | \"memoryUsage\" | \"anomalyDetection\" | \"anomalyExplorer\" | \"singleMetricViewer\" | \"dataDrift\" | \"dataFrameAnalytics\" | \"resultExplorer\" | \"analyticsMap\" | \"aiOps\" | \"logRateAnalysis\" | \"logPatternAnalysis\" | \"changePointDetections\" | \"modelManagement\" | \"nodesOverview\" | \"fileUpload\" | \"indexDataVisualizer\" | \"calendarSettings\" | \"filterListsSettings\"" ], "path": "packages/deeplinks/ml/deep_links.ts", "deprecated": false, diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 9198f06621712..d1ccd8f812b73 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index 52036d59a1007..6fa2ef8481a46 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 060f909f83038..945a48fabcd40 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index 9778918e63b41..b35ca5f35c7bc 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index a00dd318fb717..d0d6f578750c6 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 36cea36753dcb..74e33fd3c1b4f 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index 80bc8af24e661..757e08844d51c 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index d226d9dea421c..857ac8bfd0f68 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 3ab2a0209bb58..26d618fc21f7d 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 680deb5ddc9e8..90a5c2c6ef01a 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index bb52a9c342d49..cec50c704bf40 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 2380078d42c51..5490096ab2f27 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 635e04c6fb4bf..a6bb36e5be29f 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 26830e06ed37b..6dc4d3c51169d 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index dc03a37458a27..4628cdd1f32fd 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index d95fd5beef4a6..40c7cbda1705e 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 2537fc6e76dc3..2e9d151750962 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index 562bd8c3c6cfd..e26116b23e58b 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 0205eee7c609f..8bb7d5a8c3807 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index e24e8838b51aa..c501377bb892c 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index c89eca005394e..cd17cc289604a 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 31b13b67c348b..2048a40ebce14 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 94d976f79f62f..bb140d39504af 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index faf29ba51cee4..16740f84f49de 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 56964117217f9..e74180f687d45 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 95c93d573b643..65404615a78c0 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index d132272196907..6d1073d60d951 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index 9e95a92f3c220..154ce60824d8c 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.devdocs.json b/api_docs/kbn_expandable_flyout.devdocs.json index 3bf8bc5f09030..261654cf26f16 100644 --- a/api_docs/kbn_expandable_flyout.devdocs.json +++ b/api_docs/kbn_expandable_flyout.devdocs.json @@ -29,7 +29,7 @@ "\nExpandable flyout UI React component.\nDisplays 3 sections (right, left, preview) depending on the panels in the context.\n\nThe behavior expects that the left and preview sections should only be displayed is a right section\nis already rendered." ], "signature": [ - "{ ({ registeredPanels, handleOnFlyoutClosed, ...flyoutProps }: React.PropsWithChildren<", + "{ ({ registeredPanels, ...flyoutProps }: React.PropsWithChildren<", { "pluginId": "@kbn/expandable-flyout", "scope": "common", @@ -48,7 +48,7 @@ "id": "def-common.ExpandableFlyout.$1", "type": "CompoundType", "tags": [], - "label": "{\n registeredPanels,\n handleOnFlyoutClosed,\n ...flyoutProps\n}", + "label": "{\n registeredPanels,\n ...flyoutProps\n}", "description": [], "signature": [ "React.PropsWithChildren<", @@ -77,41 +77,32 @@ "tags": [], "label": "ExpandableFlyoutProvider", "description": [ - "\nWrap your plugin with this context for the ExpandableFlyout React component." + "\nWrap your plugin with this context for the ExpandableFlyout React component.\nStorage property allows you to specify how the flyout state works internally.\nWith \"url\", it will be persisted into url and thus allow for deep linking & will survive webpage reloads.\n\"memory\" is based on useReducer hook. The state is saved internally to the package. which means it will not be\npersisted when sharing url or reloading browser pages." ], "signature": [ - "React.ForwardRefExoticComponent<", - "ExpandableFlyoutProviderProps", - " & React.RefAttributes<", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.ExpandableFlyoutApi", - "text": "ExpandableFlyoutApi" - }, - ">>" + "({ children, storage, }: React.PropsWithChildren>) => JSX.Element" ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", + "path": "packages/kbn-expandable-flyout/src/provider.tsx", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "@kbn/expandable-flyout", "id": "def-common.ExpandableFlyoutProvider.$1", - "type": "Uncategorized", + "type": "CompoundType", "tags": [], - "label": "props", + "label": "{\n children,\n storage = 'url',\n}", "description": [], "signature": [ - "P" + "React.PropsWithChildren>" ], - "path": "node_modules/@types/react/index.d.ts", + "path": "packages/kbn-expandable-flyout/src/provider.tsx", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { @@ -125,13 +116,7 @@ ], "signature": [ "() => ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.ExpandableFlyoutContext", - "text": "ExpandableFlyoutContext" - } + "ExpandableFlyoutContextValue" ], "path": "packages/kbn-expandable-flyout/src/context.tsx", "deprecated": false, @@ -142,389 +127,6 @@ } ], "interfaces": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext", - "type": "Interface", - "tags": [], - "label": "ExpandableFlyoutContext", - "description": [], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.panels", - "type": "Object", - "tags": [], - "label": "panels", - "description": [ - "\nRight, left and preview panels" - ], - "signature": [ - "State" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openFlyout", - "type": "Function", - "tags": [], - "label": "openFlyout", - "description": [ - "\nOpen the flyout with left, right and/or preview panels" - ], - "signature": [ - "(panels: { left?: ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - " | undefined; right?: ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - " | undefined; preview?: ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - " | undefined; }) => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openFlyout.$1", - "type": "Object", - "tags": [], - "label": "panels", - "description": [], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openFlyout.$1.left", - "type": "Object", - "tags": [], - "label": "left", - "description": [], - "signature": [ - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - " | undefined" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openFlyout.$1.right", - "type": "Object", - "tags": [], - "label": "right", - "description": [], - "signature": [ - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - " | undefined" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openFlyout.$1.preview", - "type": "Object", - "tags": [], - "label": "preview", - "description": [], - "signature": [ - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - " | undefined" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openRightPanel", - "type": "Function", - "tags": [], - "label": "openRightPanel", - "description": [ - "\nReplaces the current right panel with a new one" - ], - "signature": [ - "(panel: ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - ") => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openRightPanel.$1", - "type": "Object", - "tags": [], - "label": "panel", - "description": [], - "signature": [ - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - } - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openLeftPanel", - "type": "Function", - "tags": [], - "label": "openLeftPanel", - "description": [ - "\nReplaces the current left panel with a new one" - ], - "signature": [ - "(panel: ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - ") => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openLeftPanel.$1", - "type": "Object", - "tags": [], - "label": "panel", - "description": [], - "signature": [ - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - } - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openPreviewPanel", - "type": "Function", - "tags": [], - "label": "openPreviewPanel", - "description": [ - "\nAdd a new preview panel to the list of current preview panels" - ], - "signature": [ - "(panel: ", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - }, - ") => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.openPreviewPanel.$1", - "type": "Object", - "tags": [], - "label": "panel", - "description": [], - "signature": [ - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.FlyoutPanelProps", - "text": "FlyoutPanelProps" - } - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.closeRightPanel", - "type": "Function", - "tags": [], - "label": "closeRightPanel", - "description": [ - "\nCloses right panel" - ], - "signature": [ - "() => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.closeLeftPanel", - "type": "Function", - "tags": [], - "label": "closeLeftPanel", - "description": [ - "\nCloses left panel" - ], - "signature": [ - "() => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.closePreviewPanel", - "type": "Function", - "tags": [], - "label": "closePreviewPanel", - "description": [ - "\nCloses all preview panels" - ], - "signature": [ - "() => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.previousPreviewPanel", - "type": "Function", - "tags": [], - "label": "previousPreviewPanel", - "description": [ - "\nGo back to previous preview panel" - ], - "signature": [ - "() => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutContext.closeFlyout", - "type": "Function", - "tags": [], - "label": "closeFlyout", - "description": [ - "\nClose all panels and closes flyout" - ], - "signature": [ - "() => void" - ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/expandable-flyout", "id": "def-common.ExpandableFlyoutProps", @@ -564,24 +166,6 @@ "path": "packages/kbn-expandable-flyout/src/index.tsx", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutProps.handleOnFlyoutClosed", - "type": "Function", - "tags": [], - "label": "handleOnFlyoutClosed", - "description": [ - "\nPropagate out EuiFlyout onClose event" - ], - "signature": [ - "(() => void) | undefined" - ], - "path": "packages/kbn-expandable-flyout/src/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] } ], "initialIsOpen": false @@ -716,25 +300,15 @@ "misc": [ { "parentPluginId": "@kbn/expandable-flyout", - "id": "def-common.ExpandableFlyoutApi", - "type": "Type", + "id": "def-common.EXPANDABLE_FLYOUT_URL_KEY", + "type": "string", "tags": [], - "label": "ExpandableFlyoutApi", + "label": "EXPANDABLE_FLYOUT_URL_KEY", "description": [], "signature": [ - "Pick<", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.ExpandableFlyoutContext", - "text": "ExpandableFlyoutContext" - }, - ", \"openFlyout\"> & { getState: () => ", - "State", - "; }" + "\"eventFlyout\"" ], - "path": "packages/kbn-expandable-flyout/src/context.tsx", + "path": "packages/kbn-expandable-flyout/src/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -750,13 +324,7 @@ "description": [], "signature": [ "React.Context<", - { - "pluginId": "@kbn/expandable-flyout", - "scope": "common", - "docId": "kibKbnExpandableFlyoutPluginApi", - "section": "def-common.ExpandableFlyoutContext", - "text": "ExpandableFlyoutContext" - }, + "ExpandableFlyoutContextValue", " | undefined>" ], "path": "packages/kbn-expandable-flyout/src/context.tsx", diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index 45dd6c7212464..0024b94a08e81 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-threat-hunting-investigations](https://github.com/org | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 36 | 0 | 14 | 3 | +| 17 | 0 | 7 | 2 | ## Common diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 83e072481f134..61a7699472a1d 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index e4dca45ac4b72..ed0a8e8e359d5 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index e29168013d1b8..8e26d9cfd6516 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index a3138c1241d79..1fb724d932087 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 586c15b08046f..54c17be8aed71 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index 7f7b12d44d331..48e703d0ab2bc 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index ac140c0307a45..e7b951b5494f4 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 6ed812b3a266d..a6f4c014832b8 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index a84c2ea4d5781..e889afeeca10f 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 7ab4db1e524de..34fd444a0cdd5 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 58b20cd1e94cc..11a5cfed91715 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 522c96571cfbf..96e18ace828a3 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 955de6655dfbc..698b76029baff 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 527e25b2ea519..f2cfc60b6cc35 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index e39ee266f7db0..4914bd5256620 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 09c4d08fedc2c..801ce2a5fb2e7 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index e2227727fea23..a066eb18d2015 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 25b7ebdc09a44..5e90bdc06b7a1 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 7b43897ad2e5e..471b6d647880a 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 7e878243736b6..4ce54e17821c0 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 956d5a062ee2e..af81942f0e0b3 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index bdaae4e384fda..8750cb250800e 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 70b765256b534..29da3ca6fa8f6 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 18c78a1941839..8aa26793bb40c 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.devdocs.json b/api_docs/kbn_lens_embeddable_utils.devdocs.json index 9c3efd71d2cf9..0e057402df29b 100644 --- a/api_docs/kbn_lens_embeddable_utils.devdocs.json +++ b/api_docs/kbn_lens_embeddable_utils.devdocs.json @@ -1,10 +1,26 @@ { "id": "@kbn/lens-embeddable-utils", "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { "classes": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn", + "id": "def-common.FormulaColumn", "type": "Class", "tags": [], "label": "FormulaColumn", @@ -12,17 +28,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.FormulaColumn", + "section": "def-common.FormulaColumn", "text": "FormulaColumn" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartColumn", + "section": "def-common.ChartColumn", "text": "ChartColumn" } ], @@ -32,7 +48,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.Unnamed", + "id": "def-common.FormulaColumn.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -46,7 +62,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.Unnamed.$1", + "id": "def-common.FormulaColumn.Unnamed.$1", "type": "CompoundType", "tags": [], "label": "valueConfig", @@ -54,9 +70,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.FormulaValueConfig", + "section": "def-common.FormulaValueConfig", "text": "FormulaValueConfig" } ], @@ -70,7 +86,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.getValueConfig", + "id": "def-common.FormulaColumn.getValueConfig", "type": "Function", "tags": [], "label": "getValueConfig", @@ -79,9 +95,9 @@ "() => ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.FormulaValueConfig", + "section": "def-common.FormulaValueConfig", "text": "FormulaValueConfig" } ], @@ -93,7 +109,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.getData", + "id": "def-common.FormulaColumn.getData", "type": "Function", "tags": [], "label": "getData", @@ -138,7 +154,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.getData.$1", + "id": "def-common.FormulaColumn.getData.$1", "type": "string", "tags": [], "label": "id", @@ -153,7 +169,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.getData.$2", + "id": "def-common.FormulaColumn.getData.$2", "type": "Object", "tags": [], "label": "baseLayer", @@ -174,7 +190,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.getData.$3", + "id": "def-common.FormulaColumn.getData.$3", "type": "Object", "tags": [], "label": "dataView", @@ -195,7 +211,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaColumn.getData.$4", + "id": "def-common.FormulaColumn.getData.$4", "type": "Object", "tags": [], "label": "formulaAPI", @@ -222,7 +238,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensAttributesBuilder", + "id": "def-common.LensAttributesBuilder", "type": "Class", "tags": [], "label": "LensAttributesBuilder", @@ -230,17 +246,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.LensAttributesBuilder", + "section": "def-common.LensAttributesBuilder", "text": "LensAttributesBuilder" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.VisualizationAttributesBuilder", + "section": "def-common.VisualizationAttributesBuilder", "text": "VisualizationAttributesBuilder" } ], @@ -250,7 +266,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensAttributesBuilder.Unnamed", + "id": "def-common.LensAttributesBuilder.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -264,7 +280,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensAttributesBuilder.Unnamed.$1", + "id": "def-common.LensAttributesBuilder.Unnamed.$1", "type": "Object", "tags": [], "label": "lens", @@ -275,7 +291,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensAttributesBuilder.Unnamed.$1.visualization", + "id": "def-common.LensAttributesBuilder.Unnamed.$1.visualization", "type": "Uncategorized", "tags": [], "label": "visualization", @@ -294,7 +310,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensAttributesBuilder.build", + "id": "def-common.LensAttributesBuilder.build", "type": "Function", "tags": [], "label": "build", @@ -363,7 +379,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart", + "id": "def-common.MetricChart", "type": "Class", "tags": [], "label": "MetricChart", @@ -371,17 +387,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.MetricChart", + "section": "def-common.MetricChart", "text": "MetricChart" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.Chart", + "section": "def-common.Chart", "text": "Chart" }, "<", @@ -400,7 +416,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.Unnamed", + "id": "def-common.MetricChart.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -414,7 +430,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.Unnamed.$1", + "id": "def-common.MetricChart.Unnamed.$1", "type": "Object", "tags": [], "label": "chartConfig", @@ -422,17 +438,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartConfig", + "section": "def-common.ChartConfig", "text": "ChartConfig" }, "<", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartLayer", + "section": "def-common.ChartLayer", "text": "ChartLayer" }, "<", @@ -455,7 +471,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.getVisualizationType", + "id": "def-common.MetricChart.getVisualizationType", "type": "Function", "tags": [], "label": "getVisualizationType", @@ -471,7 +487,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.getLayers", + "id": "def-common.MetricChart.getLayers", "type": "Function", "tags": [], "label": "getLayers", @@ -495,7 +511,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.getVisualizationState", + "id": "def-common.MetricChart.getVisualizationState", "type": "Function", "tags": [], "label": "getVisualizationState", @@ -518,7 +534,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.getReferences", + "id": "def-common.MetricChart.getReferences", "type": "Function", "tags": [], "label": "getReferences", @@ -542,7 +558,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.getDataViews", + "id": "def-common.MetricChart.getDataViews", "type": "Function", "tags": [], "label": "getDataViews", @@ -566,7 +582,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricChart.getTitle", + "id": "def-common.MetricChart.getTitle", "type": "Function", "tags": [], "label": "getTitle", @@ -585,7 +601,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer", + "id": "def-common.MetricLayer", "type": "Class", "tags": [], "label": "MetricLayer", @@ -593,17 +609,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.MetricLayer", + "section": "def-common.MetricLayer", "text": "MetricLayer" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartLayer", + "section": "def-common.ChartLayer", "text": "ChartLayer" }, "<", @@ -622,7 +638,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.Unnamed", + "id": "def-common.MetricLayer.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -636,7 +652,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.Unnamed.$1", + "id": "def-common.MetricLayer.Unnamed.$1", "type": "Object", "tags": [], "label": "layerConfig", @@ -644,9 +660,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.MetricLayerConfig", + "section": "def-common.MetricLayerConfig", "text": "MetricLayerConfig" } ], @@ -660,7 +676,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayer", + "id": "def-common.MetricLayer.getLayer", "type": "Function", "tags": [], "label": "getLayer", @@ -698,7 +714,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayer.$1", + "id": "def-common.MetricLayer.getLayer.$1", "type": "string", "tags": [], "label": "layerId", @@ -713,7 +729,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayer.$2", + "id": "def-common.MetricLayer.getLayer.$2", "type": "string", "tags": [], "label": "accessorId", @@ -728,7 +744,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayer.$3", + "id": "def-common.MetricLayer.getLayer.$3", "type": "Object", "tags": [], "label": "chartDataView", @@ -749,7 +765,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayer.$4", + "id": "def-common.MetricLayer.getLayer.$4", "type": "Object", "tags": [], "label": "formulaAPI", @@ -773,7 +789,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getReference", + "id": "def-common.MetricLayer.getReference", "type": "Function", "tags": [], "label": "getReference", @@ -803,7 +819,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getReference.$1", + "id": "def-common.MetricLayer.getReference.$1", "type": "string", "tags": [], "label": "layerId", @@ -818,7 +834,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getReference.$2", + "id": "def-common.MetricLayer.getReference.$2", "type": "Object", "tags": [], "label": "chartDataView", @@ -842,7 +858,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayerConfig", + "id": "def-common.MetricLayer.getLayerConfig", "type": "Function", "tags": [], "label": "getLayerConfig", @@ -863,7 +879,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayerConfig.$1", + "id": "def-common.MetricLayer.getLayerConfig.$1", "type": "string", "tags": [], "label": "layerId", @@ -878,7 +894,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getLayerConfig.$2", + "id": "def-common.MetricLayer.getLayerConfig.$2", "type": "string", "tags": [], "label": "accessorId", @@ -896,7 +912,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getName", + "id": "def-common.MetricLayer.getName", "type": "Function", "tags": [], "label": "getName", @@ -912,7 +928,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayer.getDataView", + "id": "def-common.MetricLayer.getDataView", "type": "Function", "tags": [], "label": "getDataView", @@ -939,7 +955,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn", + "id": "def-common.StaticColumn", "type": "Class", "tags": [], "label": "StaticColumn", @@ -947,17 +963,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticColumn", + "section": "def-common.StaticColumn", "text": "StaticColumn" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticChartColumn", + "section": "def-common.StaticChartColumn", "text": "StaticChartColumn" } ], @@ -967,7 +983,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn.Unnamed", + "id": "def-common.StaticColumn.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -981,7 +997,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn.Unnamed.$1", + "id": "def-common.StaticColumn.Unnamed.$1", "type": "CompoundType", "tags": [], "label": "valueConfig", @@ -989,9 +1005,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticValueConfig", + "section": "def-common.StaticValueConfig", "text": "StaticValueConfig" } ], @@ -1005,7 +1021,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn.getValueConfig", + "id": "def-common.StaticColumn.getValueConfig", "type": "Function", "tags": [], "label": "getValueConfig", @@ -1014,9 +1030,9 @@ "() => ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticValueConfig", + "section": "def-common.StaticValueConfig", "text": "StaticValueConfig" } ], @@ -1028,7 +1044,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn.getData", + "id": "def-common.StaticColumn.getData", "type": "Function", "tags": [], "label": "getData", @@ -1057,7 +1073,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn.getData.$1", + "id": "def-common.StaticColumn.getData.$1", "type": "string", "tags": [], "label": "id", @@ -1072,7 +1088,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticColumn.getData.$2", + "id": "def-common.StaticColumn.getData.$2", "type": "Object", "tags": [], "label": "baseLayer", @@ -1099,7 +1115,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart", + "id": "def-common.XYChart", "type": "Class", "tags": [], "label": "XYChart", @@ -1107,17 +1123,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYChart", + "section": "def-common.XYChart", "text": "XYChart" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.Chart", + "section": "def-common.Chart", "text": "Chart" }, "<", @@ -1136,7 +1152,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.Unnamed", + "id": "def-common.XYChart.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -1150,7 +1166,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.Unnamed.$1", + "id": "def-common.XYChart.Unnamed.$1", "type": "CompoundType", "tags": [], "label": "chartConfig", @@ -1158,17 +1174,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartConfig", + "section": "def-common.ChartConfig", "text": "ChartConfig" }, "<", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartLayer", + "section": "def-common.ChartLayer", "text": "ChartLayer" }, "<", @@ -1182,9 +1198,9 @@ ">[]> & { visualOptions?: ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYVisualOptions", + "section": "def-common.XYVisualOptions", "text": "XYVisualOptions" }, " | undefined; }" @@ -1199,7 +1215,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.getVisualizationType", + "id": "def-common.XYChart.getVisualizationType", "type": "Function", "tags": [], "label": "getVisualizationType", @@ -1215,7 +1231,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.getLayers", + "id": "def-common.XYChart.getLayers", "type": "Function", "tags": [], "label": "getLayers", @@ -1239,7 +1255,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.getVisualizationState", + "id": "def-common.XYChart.getVisualizationState", "type": "Function", "tags": [], "label": "getVisualizationState", @@ -1262,7 +1278,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.getReferences", + "id": "def-common.XYChart.getReferences", "type": "Function", "tags": [], "label": "getReferences", @@ -1286,7 +1302,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.getDataViews", + "id": "def-common.XYChart.getDataViews", "type": "Function", "tags": [], "label": "getDataViews", @@ -1310,7 +1326,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYChart.getTitle", + "id": "def-common.XYChart.getTitle", "type": "Function", "tags": [], "label": "getTitle", @@ -1329,7 +1345,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer", + "id": "def-common.XYDataLayer", "type": "Class", "tags": [], "label": "XYDataLayer", @@ -1337,17 +1353,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYDataLayer", + "section": "def-common.XYDataLayer", "text": "XYDataLayer" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartLayer", + "section": "def-common.ChartLayer", "text": "ChartLayer" }, "<", @@ -1366,7 +1382,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.Unnamed", + "id": "def-common.XYDataLayer.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -1380,7 +1396,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.Unnamed.$1", + "id": "def-common.XYDataLayer.Unnamed.$1", "type": "Object", "tags": [], "label": "layerConfig", @@ -1388,10 +1404,10 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYLayerConfig", - "text": "XYLayerConfig" + "section": "def-common.XYDataLayerConfig", + "text": "XYDataLayerConfig" } ], "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/layers/xy_data_layer.ts", @@ -1404,7 +1420,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getName", + "id": "def-common.XYDataLayer.getName", "type": "Function", "tags": [], "label": "getName", @@ -1420,7 +1436,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getBaseLayer", + "id": "def-common.XYDataLayer.getBaseLayer", "type": "Function", "tags": [], "label": "getBaseLayer", @@ -1437,9 +1453,9 @@ ", options: ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYLayerOptions", + "section": "def-common.XYLayerOptions", "text": "XYLayerOptions" }, ") => { [x: string]: ", @@ -1466,7 +1482,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getBaseLayer.$1", + "id": "def-common.XYDataLayer.getBaseLayer.$1", "type": "Object", "tags": [], "label": "dataView", @@ -1487,7 +1503,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getBaseLayer.$2", + "id": "def-common.XYDataLayer.getBaseLayer.$2", "type": "Object", "tags": [], "label": "options", @@ -1495,9 +1511,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYLayerOptions", + "section": "def-common.XYLayerOptions", "text": "XYLayerOptions" } ], @@ -1511,7 +1527,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayer", + "id": "def-common.XYDataLayer.getLayer", "type": "Function", "tags": [], "label": "getLayer", @@ -1549,7 +1565,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayer.$1", + "id": "def-common.XYDataLayer.getLayer.$1", "type": "string", "tags": [], "label": "layerId", @@ -1564,7 +1580,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayer.$2", + "id": "def-common.XYDataLayer.getLayer.$2", "type": "string", "tags": [], "label": "accessorId", @@ -1579,7 +1595,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayer.$3", + "id": "def-common.XYDataLayer.getLayer.$3", "type": "Object", "tags": [], "label": "chartDataView", @@ -1600,7 +1616,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayer.$4", + "id": "def-common.XYDataLayer.getLayer.$4", "type": "Object", "tags": [], "label": "formulaAPI", @@ -1624,7 +1640,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getReference", + "id": "def-common.XYDataLayer.getReference", "type": "Function", "tags": [], "label": "getReference", @@ -1654,7 +1670,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getReference.$1", + "id": "def-common.XYDataLayer.getReference.$1", "type": "string", "tags": [], "label": "layerId", @@ -1669,7 +1685,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getReference.$2", + "id": "def-common.XYDataLayer.getReference.$2", "type": "Object", "tags": [], "label": "chartDataView", @@ -1693,7 +1709,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayerConfig", + "id": "def-common.XYDataLayer.getLayerConfig", "type": "Function", "tags": [], "label": "getLayerConfig", @@ -1714,7 +1730,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayerConfig.$1", + "id": "def-common.XYDataLayer.getLayerConfig.$1", "type": "string", "tags": [], "label": "layerId", @@ -1729,7 +1745,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getLayerConfig.$2", + "id": "def-common.XYDataLayer.getLayerConfig.$2", "type": "string", "tags": [], "label": "accessorId", @@ -1747,7 +1763,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYDataLayer.getDataView", + "id": "def-common.XYDataLayer.getDataView", "type": "Function", "tags": [], "label": "getDataView", @@ -1774,7 +1790,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer", + "id": "def-common.XYReferenceLinesLayer", "type": "Class", "tags": [], "label": "XYReferenceLinesLayer", @@ -1782,17 +1798,17 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYReferenceLinesLayer", + "section": "def-common.XYReferenceLinesLayer", "text": "XYReferenceLinesLayer" }, " implements ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartLayer", + "section": "def-common.ChartLayer", "text": "ChartLayer" }, "<", @@ -1811,7 +1827,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.Unnamed", + "id": "def-common.XYReferenceLinesLayer.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -1825,7 +1841,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.Unnamed.$1", + "id": "def-common.XYReferenceLinesLayer.Unnamed.$1", "type": "Object", "tags": [], "label": "layerConfig", @@ -1833,9 +1849,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYReferenceLinesLayerConfig", + "section": "def-common.XYReferenceLinesLayerConfig", "text": "XYReferenceLinesLayerConfig" } ], @@ -1849,7 +1865,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getName", + "id": "def-common.XYReferenceLinesLayer.getName", "type": "Function", "tags": [], "label": "getName", @@ -1865,7 +1881,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getLayer", + "id": "def-common.XYReferenceLinesLayer.getLayer", "type": "Function", "tags": [], "label": "getLayer", @@ -1887,7 +1903,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getLayer.$1", + "id": "def-common.XYReferenceLinesLayer.getLayer.$1", "type": "string", "tags": [], "label": "layerId", @@ -1902,7 +1918,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getLayer.$2", + "id": "def-common.XYReferenceLinesLayer.getLayer.$2", "type": "string", "tags": [], "label": "accessorId", @@ -1920,7 +1936,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getReference", + "id": "def-common.XYReferenceLinesLayer.getReference", "type": "Function", "tags": [], "label": "getReference", @@ -1950,7 +1966,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getReference.$1", + "id": "def-common.XYReferenceLinesLayer.getReference.$1", "type": "string", "tags": [], "label": "layerId", @@ -1965,7 +1981,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getReference.$2", + "id": "def-common.XYReferenceLinesLayer.getReference.$2", "type": "Object", "tags": [], "label": "chartDataView", @@ -1989,7 +2005,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getLayerConfig", + "id": "def-common.XYReferenceLinesLayer.getLayerConfig", "type": "Function", "tags": [], "label": "getLayerConfig", @@ -2010,7 +2026,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getLayerConfig.$1", + "id": "def-common.XYReferenceLinesLayer.getLayerConfig.$1", "type": "string", "tags": [], "label": "layerId", @@ -2025,7 +2041,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getLayerConfig.$2", + "id": "def-common.XYReferenceLinesLayer.getLayerConfig.$2", "type": "string", "tags": [], "label": "accessorId", @@ -2043,7 +2059,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayer.getDataView", + "id": "def-common.XYReferenceLinesLayer.getDataView", "type": "Function", "tags": [], "label": "getDataView", @@ -2073,7 +2089,7 @@ "interfaces": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.BaseChartColumn", + "id": "def-common.BaseChartColumn", "type": "Interface", "tags": [], "label": "BaseChartColumn", @@ -2081,9 +2097,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.BaseChartColumn", + "section": "def-common.BaseChartColumn", "text": "BaseChartColumn" }, "" @@ -2094,7 +2110,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.BaseChartColumn.getValueConfig", + "id": "def-common.BaseChartColumn.getValueConfig", "type": "Function", "tags": [], "label": "getValueConfig", @@ -2113,7 +2129,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart", + "id": "def-common.Chart", "type": "Interface", "tags": [], "label": "Chart", @@ -2121,9 +2137,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.Chart", + "section": "def-common.Chart", "text": "Chart" }, "" @@ -2134,7 +2150,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart.getTitle", + "id": "def-common.Chart.getTitle", "type": "Function", "tags": [], "label": "getTitle", @@ -2150,7 +2166,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart.getVisualizationType", + "id": "def-common.Chart.getVisualizationType", "type": "Function", "tags": [], "label": "getVisualizationType", @@ -2166,7 +2182,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart.getLayers", + "id": "def-common.Chart.getLayers", "type": "Function", "tags": [], "label": "getLayers", @@ -2190,7 +2206,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart.getVisualizationState", + "id": "def-common.Chart.getVisualizationState", "type": "Function", "tags": [], "label": "getVisualizationState", @@ -2206,7 +2222,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart.getReferences", + "id": "def-common.Chart.getReferences", "type": "Function", "tags": [], "label": "getReferences", @@ -2230,7 +2246,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.Chart.getDataViews", + "id": "def-common.Chart.getDataViews", "type": "Function", "tags": [], "label": "getDataViews", @@ -2257,7 +2273,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartColumn", + "id": "def-common.ChartColumn", "type": "Interface", "tags": [], "label": "ChartColumn", @@ -2265,25 +2281,25 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartColumn", + "section": "def-common.ChartColumn", "text": "ChartColumn" }, " extends ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.BaseChartColumn", + "section": "def-common.BaseChartColumn", "text": "BaseChartColumn" }, "<", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.FormulaValueConfig", + "section": "def-common.FormulaValueConfig", "text": "FormulaValueConfig" }, ">" @@ -2294,7 +2310,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartColumn.getData", + "id": "def-common.ChartColumn.getData", "type": "Function", "tags": [], "label": "getData", @@ -2339,7 +2355,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartColumn.getData.$1", + "id": "def-common.ChartColumn.getData.$1", "type": "string", "tags": [], "label": "id", @@ -2354,7 +2370,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartColumn.getData.$2", + "id": "def-common.ChartColumn.getData.$2", "type": "Object", "tags": [], "label": "baseLayer", @@ -2375,7 +2391,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartColumn.getData.$3", + "id": "def-common.ChartColumn.getData.$3", "type": "Object", "tags": [], "label": "dataView", @@ -2396,7 +2412,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartColumn.getData.$4", + "id": "def-common.ChartColumn.getData.$4", "type": "Object", "tags": [], "label": "formulaAPI", @@ -2423,7 +2439,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartConfig", + "id": "def-common.ChartConfig", "type": "Interface", "tags": [], "label": "ChartConfig", @@ -2431,9 +2447,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartConfig", + "section": "def-common.ChartConfig", "text": "ChartConfig" }, "" @@ -2444,7 +2460,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartConfig.formulaAPI", + "id": "def-common.ChartConfig.formulaAPI", "type": "Object", "tags": [], "label": "formulaAPI", @@ -2464,7 +2480,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartConfig.dataView", + "id": "def-common.ChartConfig.dataView", "type": "Object", "tags": [], "label": "dataView", @@ -2484,7 +2500,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartConfig.layers", + "id": "def-common.ChartConfig.layers", "type": "Uncategorized", "tags": [], "label": "layers", @@ -2498,7 +2514,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartConfig.title", + "id": "def-common.ChartConfig.title", "type": "string", "tags": [], "label": "title", @@ -2515,7 +2531,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer", + "id": "def-common.ChartLayer", "type": "Interface", "tags": [], "label": "ChartLayer", @@ -2523,9 +2539,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.ChartLayer", + "section": "def-common.ChartLayer", "text": "ChartLayer" }, "" @@ -2536,7 +2552,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getName", + "id": "def-common.ChartLayer.getName", "type": "Function", "tags": [], "label": "getName", @@ -2552,7 +2568,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayer", + "id": "def-common.ChartLayer.getLayer", "type": "Function", "tags": [], "label": "getLayer", @@ -2590,7 +2606,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayer.$1", + "id": "def-common.ChartLayer.getLayer.$1", "type": "string", "tags": [], "label": "layerId", @@ -2605,7 +2621,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayer.$2", + "id": "def-common.ChartLayer.getLayer.$2", "type": "string", "tags": [], "label": "accessorId", @@ -2620,7 +2636,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayer.$3", + "id": "def-common.ChartLayer.getLayer.$3", "type": "Object", "tags": [], "label": "dataView", @@ -2641,7 +2657,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayer.$4", + "id": "def-common.ChartLayer.getLayer.$4", "type": "Object", "tags": [], "label": "formulaAPI", @@ -2665,7 +2681,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getReference", + "id": "def-common.ChartLayer.getReference", "type": "Function", "tags": [], "label": "getReference", @@ -2695,7 +2711,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getReference.$1", + "id": "def-common.ChartLayer.getReference.$1", "type": "string", "tags": [], "label": "layerId", @@ -2710,7 +2726,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getReference.$2", + "id": "def-common.ChartLayer.getReference.$2", "type": "Object", "tags": [], "label": "dataView", @@ -2734,7 +2750,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayerConfig", + "id": "def-common.ChartLayer.getLayerConfig", "type": "Function", "tags": [], "label": "getLayerConfig", @@ -2748,7 +2764,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayerConfig.$1", + "id": "def-common.ChartLayer.getLayerConfig.$1", "type": "string", "tags": [], "label": "layerId", @@ -2763,7 +2779,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getLayerConfig.$2", + "id": "def-common.ChartLayer.getLayerConfig.$2", "type": "string", "tags": [], "label": "acessorId", @@ -2781,7 +2797,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.ChartLayer.getDataView", + "id": "def-common.ChartLayer.getDataView", "type": "Function", "tags": [], "label": "getDataView", @@ -2808,7 +2824,65 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerConfig", + "id": "def-common.MetricChartModel", + "type": "Interface", + "tags": [], + "label": "MetricChartModel", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.MetricChartModel", + "text": "MetricChartModel" + }, + " extends ChartModelBase" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.MetricChartModel.visualizationType", + "type": "string", + "tags": [], + "label": "visualizationType", + "description": [], + "signature": [ + "\"lnsMetric\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.MetricChartModel.layers", + "type": "Object", + "tags": [], + "label": "layers", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.MetricLayerConfig", + "text": "MetricLayerConfig" + } + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.MetricLayerConfig", "type": "Interface", "tags": [], "label": "MetricLayerConfig", @@ -2819,7 +2893,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerConfig.data", + "id": "def-common.MetricLayerConfig.data", "type": "CompoundType", "tags": [], "label": "data", @@ -2843,7 +2917,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerConfig.options", + "id": "def-common.MetricLayerConfig.options", "type": "Object", "tags": [], "label": "options", @@ -2851,9 +2925,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.MetricLayerOptions", + "section": "def-common.MetricLayerOptions", "text": "MetricLayerOptions" }, " | undefined" @@ -2864,7 +2938,21 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerConfig.dataView", + "id": "def-common.MetricLayerConfig.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"metricTrendline\" | undefined" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/layers/metric_layer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.MetricLayerConfig.dataView", "type": "Object", "tags": [], "label": "dataView", @@ -2890,7 +2978,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerOptions", + "id": "def-common.MetricLayerOptions", "type": "Interface", "tags": [], "label": "MetricLayerOptions", @@ -2901,7 +2989,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerOptions.backgroundColor", + "id": "def-common.MetricLayerOptions.backgroundColor", "type": "string", "tags": [], "label": "backgroundColor", @@ -2915,7 +3003,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerOptions.showTitle", + "id": "def-common.MetricLayerOptions.showTitle", "type": "CompoundType", "tags": [], "label": "showTitle", @@ -2929,7 +3017,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerOptions.showTrendLine", + "id": "def-common.MetricLayerOptions.showTrendLine", "type": "CompoundType", "tags": [], "label": "showTrendLine", @@ -2943,7 +3031,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.MetricLayerOptions.subtitle", + "id": "def-common.MetricLayerOptions.subtitle", "type": "string", "tags": [], "label": "subtitle", @@ -2960,7 +3048,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticChartColumn", + "id": "def-common.StaticChartColumn", "type": "Interface", "tags": [], "label": "StaticChartColumn", @@ -2968,25 +3056,25 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticChartColumn", + "section": "def-common.StaticChartColumn", "text": "StaticChartColumn" }, " extends ", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.BaseChartColumn", + "section": "def-common.BaseChartColumn", "text": "BaseChartColumn" }, "<", { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticValueConfig", + "section": "def-common.StaticValueConfig", "text": "StaticValueConfig" }, ">" @@ -2997,7 +3085,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticChartColumn.getData", + "id": "def-common.StaticChartColumn.getData", "type": "Function", "tags": [], "label": "getData", @@ -3026,7 +3114,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticChartColumn.getData.$1", + "id": "def-common.StaticChartColumn.getData.$1", "type": "string", "tags": [], "label": "id", @@ -3041,7 +3129,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticChartColumn.getData.$2", + "id": "def-common.StaticChartColumn.getData.$2", "type": "Object", "tags": [], "label": "baseLayer", @@ -3068,7 +3156,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.VisualizationAttributesBuilder", + "id": "def-common.VisualizationAttributesBuilder", "type": "Interface", "tags": [], "label": "VisualizationAttributesBuilder", @@ -3079,7 +3167,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.VisualizationAttributesBuilder.build", + "id": "def-common.VisualizationAttributesBuilder.build", "type": "Function", "tags": [], "label": "build", @@ -3148,10 +3236,90 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerConfig", + "id": "def-common.XYChartModel", "type": "Interface", "tags": [], - "label": "XYLayerConfig", + "label": "XYChartModel", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.XYChartModel", + "text": "XYChartModel" + }, + " extends ChartModelBase" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYChartModel.visualOptions", + "type": "Object", + "tags": [], + "label": "visualOptions", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.XYVisualOptions", + "text": "XYVisualOptions" + }, + " | undefined" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYChartModel.visualizationType", + "type": "string", + "tags": [], + "label": "visualizationType", + "description": [], + "signature": [ + "\"lnsXY\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYChartModel.layers", + "type": "Array", + "tags": [], + "label": "layers", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.XYLayerConfig", + "text": "XYLayerConfig" + }, + "[]" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYDataLayerConfig", + "type": "Interface", + "tags": [], + "label": "XYDataLayerConfig", "description": [], "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/layers/xy_data_layer.ts", "deprecated": false, @@ -3159,7 +3327,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerConfig.data", + "id": "def-common.XYDataLayerConfig.data", "type": "Array", "tags": [], "label": "data", @@ -3167,9 +3335,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.FormulaValueConfig", + "section": "def-common.FormulaValueConfig", "text": "FormulaValueConfig" }, "[]" @@ -3180,7 +3348,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerConfig.options", + "id": "def-common.XYDataLayerConfig.options", "type": "Object", "tags": [], "label": "options", @@ -3188,9 +3356,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.XYLayerOptions", + "section": "def-common.XYLayerOptions", "text": "XYLayerOptions" }, " | undefined" @@ -3201,7 +3369,21 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerConfig.dataView", + "id": "def-common.XYDataLayerConfig.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"data\" | undefined" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/layers/xy_data_layer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYDataLayerConfig.dataView", "type": "Object", "tags": [], "label": "dataView", @@ -3227,7 +3409,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerOptions", + "id": "def-common.XYLayerOptions", "type": "Interface", "tags": [], "label": "XYLayerOptions", @@ -3238,7 +3420,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerOptions.breakdown", + "id": "def-common.XYLayerOptions.breakdown", "type": "Object", "tags": [], "label": "breakdown", @@ -3252,7 +3434,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerOptions.buckets", + "id": "def-common.XYLayerOptions.buckets", "type": "Object", "tags": [], "label": "buckets", @@ -3266,7 +3448,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYLayerOptions.seriesType", + "id": "def-common.XYLayerOptions.seriesType", "type": "CompoundType", "tags": [], "label": "seriesType", @@ -3290,7 +3472,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayerConfig", + "id": "def-common.XYReferenceLinesLayerConfig", "type": "Interface", "tags": [], "label": "XYReferenceLinesLayerConfig", @@ -3301,7 +3483,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayerConfig.data", + "id": "def-common.XYReferenceLinesLayerConfig.data", "type": "Array", "tags": [], "label": "data", @@ -3309,9 +3491,9 @@ "signature": [ { "pluginId": "@kbn/lens-embeddable-utils", - "scope": "public", + "scope": "common", "docId": "kibKbnLensEmbeddableUtilsPluginApi", - "section": "def-public.StaticValueConfig", + "section": "def-common.StaticValueConfig", "text": "StaticValueConfig" }, "[]" @@ -3322,7 +3504,21 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYReferenceLinesLayerConfig.dataView", + "id": "def-common.XYReferenceLinesLayerConfig.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"referenceLine\" | undefined" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/layers/xy_reference_lines_layer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYReferenceLinesLayerConfig.dataView", "type": "Object", "tags": [], "label": "dataView", @@ -3348,7 +3544,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions", + "id": "def-common.XYVisualOptions", "type": "Interface", "tags": [], "label": "XYVisualOptions", @@ -3359,7 +3555,7 @@ "children": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions.lineInterpolation", + "id": "def-common.XYVisualOptions.lineInterpolation", "type": "CompoundType", "tags": [], "label": "lineInterpolation", @@ -3380,7 +3576,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions.missingValues", + "id": "def-common.XYVisualOptions.missingValues", "type": "CompoundType", "tags": [], "label": "missingValues", @@ -3401,7 +3597,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions.endValues", + "id": "def-common.XYVisualOptions.endValues", "type": "CompoundType", "tags": [], "label": "endValues", @@ -3422,7 +3618,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions.showDottedLine", + "id": "def-common.XYVisualOptions.showDottedLine", "type": "CompoundType", "tags": [], "label": "showDottedLine", @@ -3436,7 +3632,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions.valueLabels", + "id": "def-common.XYVisualOptions.valueLabels", "type": "CompoundType", "tags": [], "label": "valueLabels", @@ -3457,7 +3653,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.XYVisualOptions.axisTitlesVisibilitySettings", + "id": "def-common.XYVisualOptions.axisTitlesVisibilitySettings", "type": "Object", "tags": [], "label": "axisTitlesVisibilitySettings", @@ -3475,6 +3671,48 @@ "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYVisualOptions.legend", + "type": "Object", + "tags": [], + "label": "legend", + "description": [], + "signature": [ + { + "pluginId": "expressionXY", + "scope": "common", + "docId": "kibExpressionXYPluginApi", + "section": "def-common.LegendConfig", + "text": "LegendConfig" + }, + " | undefined" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYVisualOptions.yLeftExtent", + "type": "Object", + "tags": [], + "label": "yLeftExtent", + "description": [], + "signature": [ + { + "pluginId": "expressionXY", + "scope": "common", + "docId": "kibExpressionXYPluginApi", + "section": "def-common.AxisExtentConfig", + "text": "AxisExtentConfig" + }, + " | undefined" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -3484,7 +3722,51 @@ "misc": [ { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.FormulaValueConfig", + "id": "def-common.ChartModel", + "type": "Type", + "tags": [], + "label": "ChartModel", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.XYChartModel", + "text": "XYChartModel" + }, + " | ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.MetricChartModel", + "text": "MetricChartModel" + } + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.ChartTypes", + "type": "Type", + "tags": [], + "label": "ChartTypes", + "description": [], + "signature": [ + "\"lnsXY\" | \"lnsMetric\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.FormulaValueConfig", "type": "Type", "tags": [], "label": "FormulaValueConfig", @@ -3509,7 +3791,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensAttributes", + "id": "def-common.LensAttributes", "type": "Type", "tags": [], "label": "LensAttributes", @@ -3574,7 +3856,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensLayerConfig", + "id": "def-common.LensLayerConfig", "type": "Type", "tags": [], "label": "LensLayerConfig", @@ -3603,7 +3885,7 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.LensVisualizationState", + "id": "def-common.LensVisualizationState", "type": "Type", "tags": [], "label": "LensVisualizationState", @@ -3632,7 +3914,37 @@ }, { "parentPluginId": "@kbn/lens-embeddable-utils", - "id": "def-public.StaticValueConfig", + "id": "def-common.METRIC_ID", + "type": "string", + "tags": [], + "label": "METRIC_ID", + "description": [], + "signature": [ + "\"lnsMetric\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.METRIC_TREND_LINE_ID", + "type": "string", + "tags": [], + "label": "METRIC_TREND_LINE_ID", + "description": [], + "signature": [ + "\"metricTrendline\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.StaticValueConfig", "type": "Type", "tags": [], "label": "StaticValueConfig", @@ -3662,24 +3974,97 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.VisualizationTypes", + "type": "Type", + "tags": [], + "label": "VisualizationTypes", + "description": [], + "signature": [ + "\"lnsXY\" | \"lnsMetric\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XY_DATA_ID", + "type": "string", + "tags": [], + "label": "XY_DATA_ID", + "description": [], + "signature": [ + "\"data\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XY_ID", + "type": "string", + "tags": [], + "label": "XY_ID", + "description": [], + "signature": [ + "\"lnsXY\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XY_REFERENCE_LINE_ID", + "type": "string", + "tags": [], + "label": "XY_REFERENCE_LINE_ID", + "description": [], + "signature": [ + "\"referenceLine\"" + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/lens-embeddable-utils", + "id": "def-common.XYLayerConfig", + "type": "Type", + "tags": [], + "label": "XYLayerConfig", + "description": [], + "signature": [ + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.XYDataLayerConfig", + "text": "XYDataLayerConfig" + }, + " | ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.XYReferenceLinesLayerConfig", + "text": "XYReferenceLinesLayerConfig" + } + ], + "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [] - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index 215ddca70a7aa..444e8aa192a3a 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; @@ -21,16 +21,16 @@ Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 160 | 0 | 157 | 0 | +| 181 | 0 | 178 | 0 | -## Client +## Common ### Classes - + ### Interfaces - + ### Consts, variables and types - + diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 88bbc6c8776ae..258d5b6f24f46 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index f508080eb9bdf..dc9b6b3150616 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index eb42dd07b52c2..f713eeabee9a8 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 047b9b9550268..4411911807f05 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index a29a4ced8c114..1dd72d453a227 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index 31e8f7eab4037..6b62063200b90 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index 8bc4d3e144909..d4014299bbab1 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index 84e51274852aa..1ff673def2a48 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index 17ec1646f212a..82aa483a88d15 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index a4b7ca08fe65d..8cfa9fd16527b 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 11d5ede436e8c..15d25c8801c09 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 7518e23e0a1d9..58cf8c3f44976 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index df1fbf06b9ce3..c2e263c1714cd 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index cb7f6b293ceee..88596196994f0 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index ca5d58d41c645..2fc4560154a25 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 3875c9302ac74..1018084a990fb 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 8a1ed49a26abf..ac37001e757dc 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 9a5c7eda8d197..f3af8b9de7dd8 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index 72e2ee3636ed2..31db2717803a1 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index bfabd0c6aec2f..cbd2da3e96fae 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index f232b83861ff3..ede27108bf02d 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index edc981befac48..8491fa12a1ece 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index a3b02f066faff..785567f3147bc 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 72ecf9cf3af13..69710e8661ece 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index bf7373d3ac5e3..9e50479acd77a 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index 1f962e627b0bb..9e7cc6539c670 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index a9a8ff3a0fd52..9e6d21160cec7 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 4fbcccff05654..5a2715681558f 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 8a74551d5f45f..038ab1efddabd 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index 2ba2dadb3467a..b05cb91ba3146 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 857fb78d44801..e7649214c1a6c 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index ab97c60353baf..2c2878c7ca3fd 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 8d15714382878..b948e6ac85d69 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 9223d85f80247..28078503c0d08 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index ba7af23a85fb4..fa31d49657453 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index fc0a7748ac29e..c741149cfe0bd 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index 726adc938ccf1..099cb98ecfc48 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 16cf5840d0716..eeab897caed42 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 7f33035fb9ee8..7d40f1ee9f9e2 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 8cc56f0fde91c..7bb3c04a88f8d 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index dc802946776d4..5d05a048aa6c4 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index d575e02197160..904e89ca3379f 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 839eced0d1936..4b8c891e881b5 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index e77b3c9415532..957580976e43e 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 29d921436e9ba..d7d691687298e 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index 55380c62dc322..931018772abc9 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index ee8b1e8c8e28b..ca67fdc5bf37a 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 7d3bd57cd7210..55384b950589f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index f31d70b04c3ab..c2639f44aae67 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 0cdc03682e66e..53ea16b0bee40 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index 1beaa0f954e67..507a2a0d32bad 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index df168563f0ba5..52d17084784b1 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 10717334d1710..53daf1b0f9a17 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 875201931aa24..066193cfb895b 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index 2292213a4528c..27cf494642fea 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 1277576052e08..784498ebfe60c 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 5ffca3d430ba1..ede6c0738834a 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 9087a805d657b..94f6d696c451e 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index 59b23f6df8109..9d4596174517e 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index ce31c33462da8..b5c3921d9cf28 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index 1d92959338903..381c6741f5ea2 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index 540c460f9813b..10df3d702aeee 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index daadb05f5f41d..ae52fea76c35c 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index a69c63ada442b..3705c1375b803 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index 34891592a786a..128ef40be6b20 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index 74d12575c10d6..3aa3ed08856bb 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index ab784f18c1285..0d37da7bb0cca 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index 8b8407d4bf763..5e08261fb9b96 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index c59eefbe104a6..1d2381fc2499a 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index 9f0a7e4255f87..f9e66552af9a4 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index 01805a6bdead6..0ddcd48b8f985 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 85be70e428046..36098e6071d13 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index 0c0ea5b037ffd..1bf7b8a9fabb2 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index 96172179c1975..58aec45a05f13 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index dbb5afce62d84..61d9caec82481 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index a142738f435f0..e9b0d25a405a0 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index 0830f80caaec9..9775ce1ed0c69 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 015b247822184..a44c31594d964 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index a95c89241eb74..0a38a48871ad0 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 62da68f9f93b8..cd139ce140473 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 0bb1a080f2441..aaff9bf4c53f4 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 0133a8963358d..7acb5ce6a8b8b 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index be7d7b44041e3..537e0173401e3 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index cbdac106bdab6..e9279bf55d617 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 38da50b0435e2..93448fbda102c 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 3e2bb12fd01ed..a6ca8d07c7ba4 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 265114bb7b6d0..cb37175069b3f 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index 3ca91debf2962..f2400ddc922d9 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index 6734304de4b08..d62cef84d82d9 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 43a75a27bcb3d..4a60c98b1e5ab 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 43997bd610293..32bd6564adacd 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index 370cb7d79f10d..1eb46f833864a 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 0e36b5dcc82df..cfc7926082449 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index fa04474abf9a3..857d03a744bcd 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index eb1cb46194c85..f084e5d5296c4 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 88a7c3baaec79..d9549f9ea4388 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 7995b7dc67dd9..b3969163b60ee 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index 1286e880af8dc..b12fdf957f8e0 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 3ef05efaadd6a..df6c533fa6272 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 025c0b636aa50..07272baf42abb 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 6bfb989cc5c99..fa23b1b70eae2 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 943279a65d589..61001a45e80fc 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 4509118c02b1a..a93ee5590222e 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 675cc0fed06f1..5c1c80ad79444 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 26c3eb9fd1a0d..56e93145a3bb7 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 693c3d2251d23..cad882e691d59 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 25ed93c63bc3e..37a50fd6a3a77 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index f1555e407499d..ee12be1ed970f 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 83cdf36c0e836..52e9b24e2b767 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 12168d8190fe7..25de7f9cba58e 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index dae69389a62b0..64d1d17a54db5 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index f1c3649f7ac20..10cb8a5d44857 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index e3629a39e9115..b2383c28bf25c 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index 528a3d4c4b2b4..cebc5f02a8dad 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index bcba765b8b132..ab8a54eec9342 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index e62db2ca40efd..7a46180ca177b 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 6e51ed05a4451..be734c34e96e4 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 93c7bd4f514b2..31a4f2e39f36f 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index aeeb34806f7a5..fb4e26d38a876 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index fcc1522198719..b7ec3b489ab0c 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index da7c1593fd8ee..36b512721a893 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 5b3c0ce9180d8..c501c4d3ddc38 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index f2d81f1964003..c5147c824b09e 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 501e438769d68..285197a8b51e5 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index cbefde71b2dcc..f7e32e78c585c 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index cd5de2539a5fc..f77d44850184c 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index e50e1a184f953..bca2eab153f25 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 89108f8f6c74d..d432411465020 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index c4abc6c3030c9..aab6ea5cd84ce 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 95fa6916cfd5a..856a25a08df72 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 11f220328b471..596230571e9fd 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 67835d52decdb..ed9aae8ef7d0d 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 62d287cb51626..2048aec0debbb 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 0b3e0d8fa01be..09de5e2cfe61c 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index b178e47be27d1..5ec9c4b196f6d 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 7ca3043a0379a..2448791a4146c 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 6c1d9078effa1..c36c3b6c8760a 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index d5aaed84fa92f..4293599c76d90 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 4684ab2f3b6b6..70c876bd4f7ac 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index a0694f3aa07f3..323dcad480f42 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index f7b4c45615937..fb612e4585995 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 15560c5282540..3e6364b1c4675 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index fbbff539e949d..4f3a2c13edbdc 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index e33f7c99e1025..da09c8e69a307 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index e5b3120e6640e..0f0ff04a56a1e 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 8b3ea65ba6d1a..787911cdc65be 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index cb54b848b3415..3042faa7ee817 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 0584ab1f33ac4..e56e338f60be6 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index d09c5a723b29b..d41bd6ec9990f 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 3b00ec9d17e02..c612691374265 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index beb6a227c7310..d47175301dec8 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 64b477d1adb9a..d10af676e4a46 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 0e996dd1c5525..8b9d7259bad84 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 3fb0bcc60df73..855be2acc50f5 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 63b0d1d980f75..c6685577e26c3 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 95790893c57c4..8e7dfc7d5d3ca 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 75a29e7ac932b..b24c4afbe3c8c 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index c4ad61dd837a7..c803e70043940 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index ecb5b352674e1..11c90e5068b81 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index feb38d8322059..f944ad332b77a 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 169fd1a9acf9e..11ce01c4f4c6f 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 1dd1f32b2f6bf..040d86a4f5e36 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index a8f8395a98c3f..a0d3c8adf9131 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index a7682f42eff71..c35d91e82f512 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 965e4159cbf66..923c66decde98 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 15a7ac54189ee..78969b19a4bcc 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index 10666168c60de..c890ae62d896b 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 2c2ac6977b613..95bdcadd9bf86 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index eb56a5e6d8111..df22157de1832 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 64f2ca72e0f6c..4225f53123e17 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index a236fa416ab5b..61a59cbb2b3a6 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index d66a0a07c473a..0cf917e4c78c8 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 535c3a53c1516..0ba144df2fccc 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index 8a805b9c34be1..ae7f8d5ac9370 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index d8b73a965e507..83028614249b0 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index 5c15a0778052f..c6a88b4050cc6 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 296276d466cc5..db812d5b8cbf1 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_url_state.devdocs.json b/api_docs/kbn_url_state.devdocs.json index e7659b142aa8e..aebb4ed9e0018 100644 --- a/api_docs/kbn_url_state.devdocs.json +++ b/api_docs/kbn_url_state.devdocs.json @@ -21,67 +21,50 @@ "functions": [ { "parentPluginId": "@kbn/url-state", - "id": "def-common.useSyncToUrl", + "id": "def-common.useUrlState", "type": "Function", "tags": [], - "label": "useSyncToUrl", + "label": "useUrlState", "description": [ - "\nSync any object with browser query string using @knb/rison" + "\nThis hook stores state in the URL, but with a namespace to avoid collisions with other values in the URL.\nIt also batches updates to the URL to avoid excessive history entries.\nWith it, you can store state in the URL and have it persist across page refreshes.\nThe state is stored in the URL as a Rison encoded object.\n\nExample: when called like this `const [value, setValue] = useUrlState('myNamespace', 'myKey');`\nthe state will be stored in the URL like this: `?myNamespace=(myKey:!n)`\n\nState is not cleared from the URL when the hook is unmounted and this is by design.\nIf you want it to be cleared, you can do it manually by calling `setValue(undefined)`.\n" ], "signature": [ - "(key: string, restore: (data: TValueToSerialize) => void, cleanupOnHistoryNavigation?: boolean) => (valueToSerialize?: TValueToSerialize | undefined) => void" + "(urlNamespace: string, key: string) => readonly [T | undefined, (updatedValue: T | undefined) => void]" ], - "path": "packages/kbn-url-state/use_sync_to_url.ts", + "path": "packages/kbn-url-state/index.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/url-state", - "id": "def-common.useSyncToUrl.$1", + "id": "def-common.useUrlState.$1", "type": "string", "tags": [], - "label": "key", + "label": "urlNamespace", "description": [ - "query string param to use" + "actual top level query param key" ], "signature": [ "string" ], - "path": "packages/kbn-url-state/use_sync_to_url.ts", + "path": "packages/kbn-url-state/index.ts", "deprecated": false, "trackAdoption": false, "isRequired": true }, { "parentPluginId": "@kbn/url-state", - "id": "def-common.useSyncToUrl.$2", - "type": "Function", - "tags": [], - "label": "restore", - "description": [ - "use this to handle restored state" - ], - "signature": [ - "(data: TValueToSerialize) => void" - ], - "path": "packages/kbn-url-state/use_sync_to_url.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/url-state", - "id": "def-common.useSyncToUrl.$3", - "type": "boolean", + "id": "def-common.useUrlState.$2", + "type": "string", "tags": [], - "label": "cleanupOnHistoryNavigation", + "label": "key", "description": [ - "use history events to cleanup state on back / forward naviation. true by default" + "sub key of the query param" ], "signature": [ - "boolean" + "string" ], - "path": "packages/kbn-url-state/use_sync_to_url.ts", + "path": "packages/kbn-url-state/index.ts", "deprecated": false, "trackAdoption": false, "isRequired": true diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index b792fa0b69c57..be2af7bac86d9 100644 --- a/api_docs/kbn_url_state.mdx +++ b/api_docs/kbn_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-url-state title: "@kbn/url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/url-state plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/url-state'] --- import kbnUrlStateObj from './kbn_url_state.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-threat-hunting-investigations](https://github.com/org | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 4 | 0 | 0 | 0 | +| 3 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index f918fb5539694..73b2dc1a6a1f5 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 1c8d5ab8767f5..0c9560d85b480 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 052726b6c14f7..0946cecdddf1a 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 758b2caa0344c..c836097bf42e0 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index f7eafb03baf16..89fed10bbb427 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index 6ad807f619515..d1f0f43aa8ee8 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index a992da3c359a9..5d9e88815dd48 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 9a5d9821e3009..db9c0412addd3 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 7ccd7e26a3a24..80667432e1787 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 471d42d2193cf..2caa01e6bc532 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 67601786322e7..0d6aaf97ea1d7 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index b19e8f9edb68e..6ee63c2adb2bc 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index e8caae0e55d71..6ed4bf3d13c15 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 862a987c1f8c2..74e07458602dd 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index b1085b980b72a..417584980204d 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 0a9a4871f7cce..8189f3b856434 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 2fe5a5ea7a53e..4d20897de2124 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index 2bb1fb4d0b977..f6bdc884d64c9 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 9a5acc3c44e66..88be089886402 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/log_explorer.mdx b/api_docs/log_explorer.mdx index 6bac40c5a9d82..1d39d1975bd4b 100644 --- a/api_docs/log_explorer.mdx +++ b/api_docs/log_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logExplorer title: "logExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logExplorer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logExplorer'] --- import logExplorerObj from './log_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index 9a353f9acd838..f554cfa187d8e 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index ec4dcd167f44e..87737ee106f8f 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 9f99a794ac04a..034743397babe 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 0d76e7b470091..b35f974517e71 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.devdocs.json b/api_docs/metrics_data_access.devdocs.json index d4c9bae22502c..6c3c17671881b 100644 --- a/api_docs/metrics_data_access.devdocs.json +++ b/api_docs/metrics_data_access.devdocs.json @@ -550,8 +550,7 @@ "label": "findInventoryModel", "description": [], "signature": [ - "(type: \"host\" | \"container\" | \"pod\" | \"awsEC2\" | \"awsS3\" | \"awsSQS\" | \"awsRDS\") => ", - "InventoryModel" + "(type: T) => InventoryModels" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/index.ts", "deprecated": false, @@ -560,12 +559,12 @@ { "parentPluginId": "metricsDataAccess", "id": "def-common.findInventoryModel.$1", - "type": "CompoundType", + "type": "Uncategorized", "tags": [], "label": "type", "description": [], "signature": [ - "\"host\" | \"container\" | \"pod\" | \"awsEC2\" | \"awsS3\" | \"awsSQS\" | \"awsRDS\"" + "T" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/index.ts", "deprecated": false, @@ -671,7 +670,7 @@ "label": "awsEC2SnapshotMetricTypes", "description": [], "signature": [ - "(\"rx\" | \"cpu\" | \"tx\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\")[]" + "(\"rx\" | \"tx\" | \"cpu\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\")[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/aws_ec2/metrics/index.ts", "deprecated": false, @@ -686,7 +685,7 @@ "label": "awsRDSSnapshotMetricTypes", "description": [], "signature": [ - "(\"cpu\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"rdsLatency\")[]" + "(\"cpu\" | \"rdsLatency\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\")[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/aws_rds/metrics/index.ts", "deprecated": false, @@ -701,7 +700,7 @@ "label": "awsS3SnapshotMetricTypes", "description": [], "signature": [ - "(\"s3TotalRequests\" | \"s3NumberOfObjects\" | \"s3BucketSize\" | \"s3DownloadBytes\" | \"s3UploadBytes\")[]" + "(\"s3BucketSize\" | \"s3NumberOfObjects\" | \"s3TotalRequests\" | \"s3UploadBytes\" | \"s3DownloadBytes\")[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/aws_s3/metrics/index.ts", "deprecated": false, @@ -716,7 +715,7 @@ "label": "awsSQSSnapshotMetricTypes", "description": [], "signature": [ - "(\"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesSent\" | \"sqsMessagesEmpty\" | \"sqsOldestMessage\")[]" + "(\"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesEmpty\" | \"sqsMessagesSent\" | \"sqsOldestMessage\")[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/aws_sqs/metrics/index.ts", "deprecated": false, @@ -731,7 +730,7 @@ "label": "containerSnapshotMetricTypes", "description": [], "signature": [ - "(\"memory\" | \"rx\" | \"cpu\" | \"tx\")[]" + "(\"memory\" | \"rx\" | \"tx\" | \"cpu\")[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/container/metrics/index.ts", "deprecated": false, @@ -746,7 +745,7 @@ "label": "hostSnapshotMetricTypes", "description": [], "signature": [ - "(\"memory\" | \"rx\" | \"cpu\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryFree\" | \"memoryTotal\" | \"normalizedLoad1m\" | \"tx\" | \"logRate\")[]" + "(\"memory\" | \"rx\" | \"logRate\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\")[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/host/metrics/index.ts", "deprecated": false, @@ -806,8 +805,313 @@ "label": "inventoryModels", "description": [], "signature": [ + "(", + "InventoryModel", + "<", + "InventoryMetricsWithDashboards", + "<{ cpuUsage: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageIowait: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageIrq: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageNice: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageSoftirq: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageSteal: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageUser: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; cpuUsageSystem: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskIORead: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskIOWrite: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskReadThroughput: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskWriteThroughput: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskSpaceAvailability: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskSpaceAvailable: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; diskUsage: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; hostCount: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; logRate: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; normalizedLoad1m: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; load1m: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; load5m: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; load15m: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; memoryUsage: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; memoryFree: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; memoryUsed: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; memoryFreeExcludingCache: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; memoryCache: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; rx: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; tx: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.FormulaValueConfig", + "text": "FormulaValueConfig" + }, + "; }, { assetDetails: { get: ({ metricsDataView, logsDataView, }: { metricsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; logsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; }) => ", + "DashboardModel", + "; }; assetDetailsFlyout: { get: ({ metricsDataView, logsDataView, }: { metricsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; logsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; }) => ", + "DashboardModel", + "; }; hostsView: { get: ({ metricsDataView }: { metricsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; }) => ", + "DashboardModel", + "; }; kpi: { get: ({ metricsDataView, options, }: { metricsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; options?: ", + { + "pluginId": "@kbn/lens-embeddable-utils", + "scope": "common", + "docId": "kibKbnLensEmbeddableUtilsPluginApi", + "section": "def-common.MetricLayerOptions", + "text": "MetricLayerOptions" + }, + " | undefined; }) => ", + "DashboardModel", + "; }; assetDetailsKubernetesNode: { get: ({ metricsDataView }: { metricsDataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; }) => ", + "DashboardModel", + "; }; }>> | ", "InventoryModel", - "[]" + "<", + "InventoryMetrics", + ">)[]" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/index.ts", "deprecated": false, @@ -854,9 +1158,9 @@ "label": "podSnapshotMetricTypes", "description": [], "signature": [ - "(\"memory\" | \"rx\" | \"cpu\" | \"tx\")[]" + "(\"memory\" | \"rx\" | \"tx\" | \"cpu\")[]" ], - "path": "x-pack/plugins/metrics_data_access/common/inventory_models/pod/metrics/index.ts", + "path": "x-pack/plugins/metrics_data_access/common/inventory_models/kubernetes/pod/metrics/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -869,7 +1173,7 @@ "label": "SnapshotMetricType", "description": [], "signature": [ - "\"count\" | \"custom\" | \"memory\" | \"rx\" | \"cpu\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryFree\" | \"memoryTotal\" | \"normalizedLoad1m\" | \"tx\" | \"logRate\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\" | \"s3TotalRequests\" | \"s3NumberOfObjects\" | \"s3BucketSize\" | \"s3DownloadBytes\" | \"s3UploadBytes\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"rdsLatency\" | \"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesSent\" | \"sqsMessagesEmpty\" | \"sqsOldestMessage\"" + "\"count\" | \"custom\" | \"memory\" | \"rx\" | \"logRate\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"s3BucketSize\" | \"s3NumberOfObjects\" | \"s3TotalRequests\" | \"s3UploadBytes\" | \"s3DownloadBytes\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\" | \"rdsLatency\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesEmpty\" | \"sqsMessagesSent\" | \"sqsOldestMessage\"" ], "path": "x-pack/plugins/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index 2f2461b9b68c9..bc7e5c38a48fd 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 104 | 8 | 104 | 4 | +| 104 | 8 | 104 | 7 | ## Client diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 87b4da2473a9e..72b22bbbd0ae9 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index cb94d7968da0f..7fc432f800e80 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 82b128340cc10..626073f32a2c7 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 3ebf47a7cfc0b..395aed97bf47e 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index ab40985b66f6a..39abfbdd45c23 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 38f4755e1d6b1..8e4a8b2f43420 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index f26b7ebcfe26f..3ecc9a0a4a98d 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 06497348df1f9..9a346e0daa2e2 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 0e541476cce4a..6c7f0ca040bea 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 2f659f30287de..0542ec1a069c9 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_log_explorer.mdx b/api_docs/observability_log_explorer.mdx index 00ae04b0f505a..e46d60283a3b5 100644 --- a/api_docs/observability_log_explorer.mdx +++ b/api_docs/observability_log_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogExplorer title: "observabilityLogExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogExplorer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogExplorer'] --- import observabilityLogExplorerObj from './observability_log_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 05ca4c0f3f887..907d9f5b14e16 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index bdd380df7d796..a3ebc858c760a 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 2566ca1190674..e1a16e8c978cf 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index 32c0d012ca887..f211243a8e765 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 63005d80ca9de..f5e8cd53f6f4f 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 76978 | 235 | 65681 | 1613 | +| 76990 | 235 | 65701 | 1615 | ## Plugin Directory @@ -130,7 +130,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 45 | 0 | 45 | 7 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 260 | 0 | 259 | 28 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 60 | 0 | 60 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 104 | 8 | 104 | 4 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 104 | 8 | 104 | 7 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 150 | 3 | 64 | 33 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 25 | 0 | 19 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 15 | 3 | 13 | 1 | @@ -164,7 +164,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-reporting-services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 32 | 0 | 8 | 4 | | searchprofiler | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 401 | 0 | 196 | 2 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 178 | 0 | 109 | 36 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 179 | 0 | 110 | 36 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Serverless customizations for security. | 7 | 0 | 7 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 19 | 0 | 18 | 0 | @@ -350,7 +350,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 13 | 0 | 13 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 29 | 0 | 25 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 1 | 11 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 55 | 0 | 8 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 56 | 0 | 8 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | @@ -378,7 +378,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 111 | 1 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 353 | 1 | 5 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 11 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 89 | 0 | 61 | 10 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 98 | 0 | 66 | 10 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2 | 0 | 1 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | @@ -456,7 +456,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 39 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 36 | 0 | 14 | 3 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 17 | 0 | 7 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 16 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 29 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | @@ -481,7 +481,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 35 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 108 | 0 | 107 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 7 | 0 | 5 | 0 | -| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 160 | 0 | 157 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 181 | 0 | 178 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 27 | 0 | 1 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 8 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 1 | 1 | @@ -659,7 +659,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 10 | 0 | 7 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list and field stats which can be integrated into apps | 285 | 0 | 261 | 9 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 13 | 0 | 9 | 0 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 4 | 0 | 0 | 0 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 3 | 0 | 0 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 3 | 0 | 2 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 80 | 1 | 21 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 37 | 0 | 16 | 1 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 851cc51baacaf..ed22da199930a 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 5a4573b7d09e0..a900cc2a044cb 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index 864641e5cdfbb..17f3eb91b977c 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 13d2983c76f12..3c8026429eed7 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index af9ce0ea01c62..fb60565602930 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 3b48d45347337..b96914937168e 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 5ab2c44a7756b..8ef62034ded00 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index aa5cd33093911..42416a567358a 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 4bc7c38896edd..a404abd568f03 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 1a11fb5649197..8ccd2c025af49 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 65f414faecbee..9a74616a4bff4 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index aaffc31371dde..dde206679fc05 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 0f80cd7516aac..58cb016ee1ebd 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 9699f1d3a77c0..956951c3e23e0 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index f7ee5a283c576..15355dea5fb17 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index fb73fe558213b..a267adff13952 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 514ec8f2db66e..f7861086f3ea8 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index a893eeffd3155..f1940d5057635 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -1746,6 +1746,27 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "securitySolution", + "id": "def-public.TimelineModel.savedSearch", + "type": "CompoundType", + "tags": [], + "label": "savedSearch", + "description": [], + "signature": [ + { + "pluginId": "savedSearch", + "scope": "common", + "docId": "kibSavedSearchPluginApi", + "section": "def-common.SavedSearch", + "text": "SavedSearch" + }, + " | null" + ], + "path": "x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "securitySolution", "id": "def-public.TimelineModel.isDiscoverSavedSearchLoaded", diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 48d50a9e307d4..2f72f7c7100e2 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 178 | 0 | 109 | 36 | +| 179 | 0 | 110 | 36 | ## Client diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 77e2f3b709bb1..e36a236b3ac6f 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index e5bb6205f39b2..408c5aebedd75 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index de5719b4297e9..c92f4624fbb40 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index eb1b4c614a89f..ac7df6fc4844a 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index 964f8a62ef39c..f14af46ed566a 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index f476a884606a5..2758666425381 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 062290680fd80..4fd71890219a6 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 20789067030af..7713fb87cb1b0 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index bfe873fcbdd13..e400866debc3d 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 031f7a13cad0f..8c876c0bfd676 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index a440d1f197a11..aa28878d29bee 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 9954d2bc0d264..1be69adb4201a 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index ea35a897980f4..1c3449086ec82 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 7ed6d4f7f12ed..cd16b46f25913 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 0c02ad4a63660..fc46b9ffa5fd9 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 82e7d78584342..0e1991269a1db 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index 6e48843cfb435..3bc03bb278796 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index de3d17bc5fbeb..9ba91c1356c77 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 62a75fefbeaa7..caeb5575ecc38 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 5cb3f87fd5360..daa6acf82571f 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index cb32d2a992b6b..10804146f6e0d 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 701ba81cde034..d3a47de56e236 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index aa18cb1591d33..e0714fa0fa8c2 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index f2028b1eb4f9f..7c273425480bf 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 19f9c749c44d5..6f01d890dd83f 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 4f482eebda4cc..ff8bcbb2a04cb 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 531975fc114df..18be8494d22e6 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index ab2d33d5acee2..20d1ee18bc44c 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 9ed615b1d045c..18ca5a6d27bc9 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 5a15363adf97f..282326deeaf51 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index fab000edb3eb1..69579287886a0 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index f45b9b478bef7..a7049910633a1 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index d30723d79a127..3237548a03d11 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index f3f82397e2e00..54d7a2e834c20 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index d1dba2fdb2e30..807a197128d58 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index c8f89d771562f..b4b562c373450 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index c9d6b12adc448..d41cd2fdc70fa 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 407fce6e3f77e..fa2ec730b5dfc 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index a5e415b168acb..46e3f32c3834f 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 7f28cd79d6791..7a83300802e02 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 08f295704c990..8360310c94e0c 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 5882749feec76..9275b608497f0 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-11-30 +date: 2023-12-01 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/docs/apm/how-to-guides.asciidoc b/docs/apm/how-to-guides.asciidoc index 1f1670a12e6fd..fe7d6626a077c 100644 --- a/docs/apm/how-to-guides.asciidoc +++ b/docs/apm/how-to-guides.asciidoc @@ -13,6 +13,7 @@ Learn how to perform common APM app tasks. * <> * <> * <> +* <> * <> * <> * <> @@ -35,6 +36,8 @@ include::agent-explorer.asciidoc[] include::machine-learning.asciidoc[] +include::mobile-session-explorer.asciidoc[] + include::lambda.asciidoc[] include::advanced-queries.asciidoc[] diff --git a/docs/apm/images/mobile-session-error-details.png b/docs/apm/images/mobile-session-error-details.png new file mode 100644 index 0000000000000..41c0bf509514d Binary files /dev/null and b/docs/apm/images/mobile-session-error-details.png differ diff --git a/docs/apm/images/mobile-session-explorer-apm.png b/docs/apm/images/mobile-session-explorer-apm.png new file mode 100644 index 0000000000000..55fbc857901f0 Binary files /dev/null and b/docs/apm/images/mobile-session-explorer-apm.png differ diff --git a/docs/apm/images/mobile-session-explorer-nav.png b/docs/apm/images/mobile-session-explorer-nav.png new file mode 100644 index 0000000000000..d208f4091201a Binary files /dev/null and b/docs/apm/images/mobile-session-explorer-nav.png differ diff --git a/docs/apm/images/mobile-session-filter-discover.png b/docs/apm/images/mobile-session-filter-discover.png new file mode 100644 index 0000000000000..989284ba2aeac Binary files /dev/null and b/docs/apm/images/mobile-session-filter-discover.png differ diff --git a/docs/apm/mobile-errors.asciidoc b/docs/apm/mobile-errors.asciidoc new file mode 100644 index 0000000000000..df4e4f380b0c7 --- /dev/null +++ b/docs/apm/mobile-errors.asciidoc @@ -0,0 +1,36 @@ +[role="xpack"] +[[mobile-errors-crashes]] +=== Mobile errors and crashes + +TIP: {apm-guide-ref}/data-model-errors.html[Errors] are groups of exceptions with a similar exception or log message. + +The *Errors & Crashes* overview provides a high-level view of errors and crashes that APM mobile agents catch, +or that users manually report with APM agent APIs. Errors and crashes are separated into two tabs for easy differentiation. +Like errors are grouped together to make it easy to quickly see which errors are affecting your services, +and to take actions to rectify them. + + + + + +[role="screenshot"] +image::apm/images/mobile-errors-overview.png[Mobile Errors overview] + +Selecting an error group ID or error message brings you to the *Error group*. + +[role="screenshot"] +image::apm/images/mobile-error-group.png[Mobile Error group] + +The error group details page visualizes the number of error occurrences over time and compared to a recent time range. +This allows you to quickly determine if the error rate is changing or remaining constant. +You'll also see the "most affected" chart which can be oriented to 'by device' or 'by app version'. + +Further down, you'll see an Error sample. +The error shown is always the most recent to occur. +The sample includes the exception message, culprit, stack trace where the error occurred (when available), +and additional contextual information to help debug the issue--all of which can be copied with the click of a button. + +In some cases, you might also see a Transaction sample ID. +This feature allows you to make a connection between the errors and transactions, +by linking you to the specific transaction where the error occurred. +This allows you to see the whole trace, including which services the request went through. diff --git a/docs/apm/mobile-service.asciidoc b/docs/apm/mobile-service.asciidoc index aca4e4e659818..774d4592dd67a 100644 --- a/docs/apm/mobile-service.asciidoc +++ b/docs/apm/mobile-service.asciidoc @@ -9,7 +9,7 @@ to make data-driven decisions about how to improve your user experience. For example, see: -* Crash Rate (Crashes per minute) -- coming soon +* Crash Rate (Crashes per session) * Slowest App load time -- coming soon * Number of sessions * Number of HTTP requests @@ -28,6 +28,8 @@ of their mobile application environment and the impact of backend errors and bot Understand the impact of slow application load times and variations in application crash rate on user traffic (coming soon). Visualize session and HTTP trends, and see where your users are located--enabling you to optimize your infrastructure deployment and routing topology. +Note: due to the way crash rate is calculated (crashes per session) it is possible to have greater than 100% rate, due to fact that a session may contain multiple crashes. + [role="screenshot"] image::apm/images/mobile-location.png[mobile service overview centered on location map] diff --git a/docs/apm/mobile-session-explorer.asciidoc b/docs/apm/mobile-session-explorer.asciidoc new file mode 100644 index 0000000000000..92d4e4dc1fe8d --- /dev/null +++ b/docs/apm/mobile-session-explorer.asciidoc @@ -0,0 +1,43 @@ +[role="xpack] +[[mobile-session-explorer]] +=== Exploring mobile sessions with Discover +Elastic Mobile APM provides session tracking by attaching a `session.id`, a guid, to every span and event. +This allows for the recall of the activities of a specific user during a specific period of time. The best way recall +these data points is using the xref:document-explorer[Discover document explorer]. This guide will explain how to do that. + +=== Viewing sessions with Discover + +The first step is to find the relevant `session.id`. In this example, we'll walk through investigating a crash. +Since all events and spans have `session.id` attributes, a crash is no different. + +The steps to follow are: + +* copy the `session.id` from the relevant document. +* Open the Discover page. +* Select the appropriate data view (use `APM` to search all datastreams) +* set filter to the copied `session.id` + +Here we can see the `session.id` guid in the metadata viewer in the error detail view: +[role="screenshot"] +image::images/mobile-session-error-details.png[Example of session.id in error details] + +Copy this value and open the Discover page: + +[role="screenshot"] +image::images/mobile-session-explorer-nav.png[Example view of navigation to Discover] + + +set the data view. `APM` selected in the example: + +[role="screenshot"] +image::images/mobile-session-explorer-apm.png[Example view of Explorer selecting APM data view] + +filter using the `session.id`: `session.id: ""`: + +[role="screenshot"] +image::images/mobile-session-filter-discover.png[Filter Explor using session.id] + +explore all the documents associated with that session id including crashes, lifecycle events, network requests, errors, and other custom events! + + + diff --git a/package.json b/package.json index f7ca215f20fd6..08dc4ceee7a3a 100644 --- a/package.json +++ b/package.json @@ -573,6 +573,7 @@ "@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details", "@kbn/observability-alerting-test-data": "link:x-pack/packages/observability/alerting_test_data", "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", + "@kbn/observability-get-padded-alert-time-range-util": "link:x-pack/packages/observability/get_padded_alert_time_range_util", "@kbn/observability-log-explorer-plugin": "link:x-pack/plugins/observability_log_explorer", "@kbn/observability-onboarding-plugin": "link:x-pack/plugins/observability_onboarding", "@kbn/observability-plugin": "link:x-pack/plugins/observability", @@ -912,7 +913,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^6.1.0", - "elastic-apm-node": "^4.1.0", + "elastic-apm-node": "^4.2.0", "email-addresses": "^5.0.0", "execa": "^5.1.1", "expiry-js": "0.1.7", @@ -1318,7 +1319,7 @@ "@types/byte-size": "^8.1.0", "@types/chance": "^1.0.0", "@types/chroma-js": "^2.1.0", - "@types/chromedriver": "^81.0.2", + "@types/chromedriver": "^81.0.5", "@types/classnames": "^2.2.9", "@types/color": "^3.0.3", "@types/cytoscape": "^3.14.0", @@ -1425,7 +1426,7 @@ "@types/redux-logger": "^3.0.8", "@types/resolve": "^1.20.1", "@types/seedrandom": ">=2.0.0 <4.0.0", - "@types/selenium-webdriver": "^4.1.13", + "@types/selenium-webdriver": "^4.1.20", "@types/semver": "^7", "@types/set-value": "^2.0.0", "@types/sharp": "^0.30.4", @@ -1528,7 +1529,7 @@ "file-loader": "^4.2.0", "find-cypress-specs": "^1.35.1", "form-data": "^4.0.0", - "geckodriver": "^4.0.0", + "geckodriver": "^4.2.1", "gulp-brotli": "^3.0.0", "gulp-postcss": "^9.0.1", "gulp-sourcemaps": "2.6.5", @@ -1603,7 +1604,7 @@ "resolve": "^1.22.0", "rxjs-marbles": "^7.0.1", "sass-loader": "^10.4.1", - "selenium-webdriver": "^4.9.1", + "selenium-webdriver": "^4.15.0", "simple-git": "^3.16.0", "sinon": "^7.4.2", "sort-package-json": "^1.53.1", diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts index 8e08439e7450a..07ada8b7c06b5 100644 --- a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts @@ -32,6 +32,7 @@ import { ALERT_TIME_RANGE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, SPACE_IDS, @@ -190,6 +191,11 @@ export const alertFieldMap = { array: true, required: false, }, + [ALERT_WORKFLOW_ASSIGNEE_IDS]: { + type: 'keyword', + array: true, + required: false, + }, [EVENT_ACTION]: { type: 'keyword', array: false, diff --git a/packages/kbn-alerts-as-data-utils/src/schemas/generated/alert_schema.ts b/packages/kbn-alerts-as-data-utils/src/schemas/generated/alert_schema.ts index ac143adf5f5d5..5625460f269b4 100644 --- a/packages/kbn-alerts-as-data-utils/src/schemas/generated/alert_schema.ts +++ b/packages/kbn-alerts-as-data-utils/src/schemas/generated/alert_schema.ts @@ -98,6 +98,7 @@ const AlertOptional = rt.partial({ 'kibana.alert.start': schemaDate, 'kibana.alert.time_range': schemaDateRange, 'kibana.alert.url': schemaString, + 'kibana.alert.workflow_assignee_ids': schemaStringArray, 'kibana.alert.workflow_status': schemaString, 'kibana.alert.workflow_tags': schemaStringArray, 'kibana.version': schemaString, diff --git a/packages/kbn-alerts-as-data-utils/src/schemas/generated/security_schema.ts b/packages/kbn-alerts-as-data-utils/src/schemas/generated/security_schema.ts index f8648bfa4218d..a0af087b70c9f 100644 --- a/packages/kbn-alerts-as-data-utils/src/schemas/generated/security_schema.ts +++ b/packages/kbn-alerts-as-data-utils/src/schemas/generated/security_schema.ts @@ -193,6 +193,7 @@ const SecurityAlertOptional = rt.partial({ ), 'kibana.alert.time_range': schemaDateRange, 'kibana.alert.url': schemaString, + 'kibana.alert.workflow_assignee_ids': schemaStringArray, 'kibana.alert.workflow_reason': schemaString, 'kibana.alert.workflow_status': schemaString, 'kibana.alert.workflow_tags': schemaStringArray, diff --git a/packages/kbn-alerts-as-data-utils/src/search/security/fields.ts b/packages/kbn-alerts-as-data-utils/src/search/security/fields.ts index b3be5cbb62a1a..34da32b0eaa5a 100644 --- a/packages/kbn-alerts-as-data-utils/src/search/security/fields.ts +++ b/packages/kbn-alerts-as-data-utils/src/search/security/fields.ts @@ -11,6 +11,7 @@ import { ALERT_RISK_SCORE, ALERT_SEVERITY, ALERT_RULE_PARAMETERS, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_TAGS, } from '@kbn/rule-data-utils'; @@ -46,6 +47,7 @@ export const ALERT_EVENTS_FIELDS = [ ALERT_RULE_CONSUMER, '@timestamp', 'kibana.alert.ancestors.index', + ALERT_WORKFLOW_ASSIGNEE_IDS, 'kibana.alert.workflow_status', ALERT_WORKFLOW_TAGS, 'kibana.alert.group.id', diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts index bedae100bc933..3dd5ec30933c6 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/instance.ts @@ -64,6 +64,14 @@ export class Instance extends Entity { }); } + crash({ message, type }: { message: string; type?: string }) { + return new ApmError({ + ...this.fields, + 'error.type': 'crash', + 'error.exception': [{ message, ...(type ? { type } : {}) }], + 'error.grouping_name': getErrorGroupingKey(message), + }); + } error({ message, type }: { message: string; type?: string }) { return new ApmError({ ...this.fields, diff --git a/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts b/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts index d792dc35f037b..08fab85b04c0d 100644 --- a/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts +++ b/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts @@ -52,10 +52,6 @@ function options(y: Argv) { number: true, default: 1, }) - .option('versionOverride', { - describe: 'Package/observer version override', - string: true, - }) .option('logLevel', { describe: 'Log level', default: 'info', @@ -66,6 +62,10 @@ function options(y: Argv) { return arg as Record | undefined; }, }) + .option('assume-package-version', { + describe: 'Assumes passed package version to avoid calling Fleet API to install', + string: true, + }) .showHelpOnFail(false); } diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts b/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts index be0bd7ff6168a..df436fa8984e6 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/bootstrap.ts @@ -16,6 +16,8 @@ import { RunOptions } from './parse_run_cli_flags'; export async function bootstrap(runOptions: RunOptions) { const logger = createLogger(runOptions.logLevel); + let version = runOptions['assume-package-version']; + const { kibanaUrl, esUrl } = await getServiceUrls({ ...runOptions, logger }); const kibanaClient = getKibanaClient({ @@ -23,9 +25,14 @@ export async function bootstrap(runOptions: RunOptions) { logger, }); - const latestPackageVersion = await kibanaClient.fetchLatestApmPackageVersion(); + if (!version) { + version = await kibanaClient.fetchLatestApmPackageVersion(); + await kibanaClient.installApmPackage(version); + } else if (version === 'latest') { + version = await kibanaClient.fetchLatestApmPackageVersion(); + } - const version = runOptions.versionOverride || latestPackageVersion; + logger.info(`Using package version: ${version}`); const apmEsClient = getApmEsClient({ target: esUrl, @@ -40,8 +47,6 @@ export async function bootstrap(runOptions: RunOptions) { concurrency: runOptions.concurrency, }); - await kibanaClient.installApmPackage(latestPackageVersion); - if (runOptions.clean) { await apmEsClient.clean(); await logsEsClient.clean(); diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts b/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts index 64abc36c05602..3f13cae5e039c 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts @@ -36,6 +36,7 @@ async function discoverAuth(parsedTarget: Url) { async function getKibanaUrl({ target, logger }: { target: string; logger: Logger }) { try { + const isCI = process.env.CI?.toLowerCase() === 'true'; logger.debug(`Checking Kibana URL ${target} for a redirect`); const unredirectedResponse = await fetch(target, { @@ -69,7 +70,16 @@ async function getKibanaUrl({ target, logger }: { target: string; logger: Logger ); } - logger.info(`Discovered kibana running at: ${discoveredKibanaUrlWithAuth}`); + const discoveredKibanaUrlWithoutAuth = format({ + ...parsedDiscoveredUrl, + auth: undefined, + }); + + logger.info( + `Discovered kibana running at: ${ + isCI ? discoveredKibanaUrlWithoutAuth : discoveredKibanaUrlWithAuth + }` + ); return discoveredKibanaUrlWithAuth.replace(/\/$/, ''); } catch (error) { diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts b/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts index 57426902d7b64..fe047f7ebfc8a 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts @@ -38,7 +38,10 @@ function getParsedFile(flags: RunCliFlags) { } export function parseRunCliFlags(flags: RunCliFlags) { - const { logLevel } = flags; + const { logLevel, target } = flags; + if (target?.includes('.kb.')) { + throw new Error(`Target URL seems to be a Kibana URL, please provide Elasticsearch URL`); + } const parsedFile = getParsedFile(flags); let parsedLogLevel = LogLevel.info; @@ -69,7 +72,8 @@ export function parseRunCliFlags(flags: RunCliFlags) { 'kibana', 'concurrency', 'versionOverride', - 'clean' + 'clean', + 'assume-package-version' ), logLevel: parsedLogLevel, file: parsedFile, diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 8234daa4b4454..42070ca3053f1 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -520,6 +520,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { trainedModels: `${MACHINE_LEARNING_DOCS}ml-trained-models.html`, startTrainedModelsDeployment: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-model.html`, nlpElser: `${MACHINE_LEARNING_DOCS}ml-nlp-elser.html`, + nlpE5: `${MACHINE_LEARNING_DOCS}ml-nlp-e5.html`, nlpImportModel: `${MACHINE_LEARNING_DOCS}ml-nlp-import-model.html`, }, transforms: { diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index d1aec24a9b26e..7c08271478131 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -70,6 +70,9 @@ const ALERT_WORKFLOW_STATUS = `${ALERT_NAMESPACE}.workflow_status` as const; // kibana.alert.workflow_tags - user workflow alert tags const ALERT_WORKFLOW_TAGS = `${ALERT_NAMESPACE}.workflow_tags` as const; +// kibana.alert.workflow_assignee_ids - user workflow alert assignees +const ALERT_WORKFLOW_ASSIGNEE_IDS = `${ALERT_NAMESPACE}.workflow_assignee_ids` as const; + // kibana.alert.rule.category - rule type name for rule that generated this alert const ALERT_RULE_CATEGORY = `${ALERT_RULE_NAMESPACE}.category` as const; @@ -135,6 +138,7 @@ const fields = { ALERT_TIME_RANGE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, SPACE_IDS, @@ -174,6 +178,7 @@ export { ALERT_TIME_RANGE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, SPACE_IDS, diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index b387ab67750d5..951766977f05e 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -32,6 +32,7 @@ import { ALERT_STATUS, ALERT_TIME_RANGE, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, SPACE_IDS, @@ -174,6 +175,7 @@ const fields = { ALERT_STATUS, ALERT_SYSTEM_STATUS, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_REASON, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, diff --git a/packages/kbn-search-connectors/types/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts index d815c40eb435b..e29f1ebbf8a8a 100644 --- a/packages/kbn-search-connectors/types/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -2040,6 +2040,243 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record & { building_block_type?: string[]; workflow_status?: string[]; workflow_tags?: string[]; + workflow_assignee_ids?: string[]; suppression?: { docs_count: string[]; }; diff --git a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts index 17bb9f190646d..d44770d2f2096 100644 --- a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts +++ b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts @@ -38,6 +38,9 @@ const agent: jest.Mocked = { start: jest.fn().mockImplementation(() => agent), isStarted: jest.fn().mockReturnValue(false), getServiceName: jest.fn().mockReturnValue('mock-service'), + getServiceVersion: jest.fn().mockReturnValue('1.0'), + getServiceEnvironment: jest.fn().mockReturnValue('env'), + getServiceNodeName: jest.fn().mockReturnValue('mock-node-name'), setFramework: jest.fn(), addPatch: jest.fn(), removePatch: jest.fn(), diff --git a/src/cli/serve/integration_tests/invalid_config.test.ts b/src/cli/serve/integration_tests/invalid_config.test.ts index 32414fe7f89f5..ba9ee113ee766 100644 --- a/src/cli/serve/integration_tests/invalid_config.test.ts +++ b/src/cli/serve/integration_tests/invalid_config.test.ts @@ -41,7 +41,7 @@ describe('cli invalid config support', () => { .split('\n') .filter(Boolean) .map((line) => JSON.parse(line) as LogEntry) - .filter((line) => line.log.level === 'FATAL'); + .filter((line) => line.log?.level === 'FATAL'); } catch (e) { throw new Error( `error parsing log output:\n\n${e.stack}\n\nstdout: \n${stdout}\n\nstderr:\n${stderr}` diff --git a/src/dev/so_migration/compare_snapshots.ts b/src/dev/so_migration/compare_snapshots.ts index 3f5563c189138..9eecf621c13e6 100644 --- a/src/dev/so_migration/compare_snapshots.ts +++ b/src/dev/so_migration/compare_snapshots.ts @@ -45,7 +45,7 @@ async function compareSnapshots({ log.info( `Snapshots compared: ${from} <=> ${to}. ` + - `${result.hasChanges ? 'No changes' : 'Changed: ' + result.changed.join(', ')}` + `${result.hasChanges ? 'Changed: ' + result.changed.join(', ') : 'No changes'}` ); if (outputPath) { diff --git a/src/plugins/discover/public/application/doc/components/doc.test.tsx b/src/plugins/discover/public/application/doc/components/doc.test.tsx index 56eac62b0318c..79a1be2cf1751 100644 --- a/src/plugins/discover/public/application/doc/components/doc.test.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.test.tsx @@ -18,6 +18,7 @@ import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { setUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/plugin'; import { mockUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/__mocks__'; +import type { UnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/types'; const mockSearchApi = jest.fn(); @@ -68,7 +69,14 @@ async function mountDoc(update = false) { locator: { getUrl: jest.fn(() => Promise.resolve('mock-url')) }, chrome: { setBreadcrumbs: jest.fn() }, }; - setUnifiedDocViewerServices(mockUnifiedDocViewerServices); + setUnifiedDocViewerServices({ + ...mockUnifiedDocViewerServices, + data: { + search: { + search: mockSearchApi, + }, + }, + } as unknown as UnifiedDocViewerServices); await act(async () => { comp = mountWithIntl( diff --git a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts index fc6a1ae9d166e..761fb9764c82f 100644 --- a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts +++ b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts @@ -70,7 +70,7 @@ export async function loadDataView({ let fetchedDataView: DataView | null = null; // try to fetch adhoc data view first try { - fetchedDataView = fetchId ? await dataViews.get(fetchId, false, true) : null; + fetchedDataView = fetchId ? await dataViews.get(fetchId) : null; if (fetchedDataView && !fetchedDataView.isPersisted()) { return { list: dataViewList || [], diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx index 8e1c7fbc74b99..267131c33058e 100644 --- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx +++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx @@ -100,6 +100,9 @@ export function DataViewPicker({ compressed: true, ...(selectableProps ? selectableProps.searchProps : undefined), }} + listProps={{ + truncationProps: { truncation: 'middle' }, + }} > {(list, search) => ( <> diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx index d5789284b4131..ef14a6c88dcb7 100644 --- a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx +++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx @@ -141,6 +141,7 @@ export const FieldPicker = ({ isVirtualized: true, showIcons: false, bordered: true, + truncationProps: { truncation: 'middle' }, }} height="full" > diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer/doc_viewer.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer/doc_viewer.tsx index beb9a032d8c84..929ef2aa1b0c4 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer/doc_viewer.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer/doc_viewer.tsx @@ -7,16 +7,11 @@ */ import React from 'react'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; import { DocViewer } from '@kbn/unified-doc-viewer'; import { getUnifiedDocViewerServices } from '../../plugin'; export function UnifiedDocViewer(props: DocViewRenderProps) { - const services = getUnifiedDocViewerServices(); - return ( - - - - ); + const { unifiedDocViewer } = getUnifiedDocViewerServices(); + return ; } diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.test.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.test.tsx index 02f31a2e4f46c..a9c8ae7d55b14 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.test.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.test.tsx @@ -7,25 +7,20 @@ */ import React from 'react'; -import type { DataView } from '@kbn/data-views-plugin/public'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DocViewerSource } from './source'; import * as hooks from '../../hooks/use_es_doc_search'; import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting'; import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; import { JsonCodeEditorCommon } from '../json_code_editor'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { buildDataTableRecord } from '@kbn/discover-utils'; -import { of } from 'rxjs'; +import { setUnifiedDocViewerServices } from '../../plugin'; +import type { UnifiedDocViewerServices } from '../../types'; const mockDataView = { getComputedFields: () => [], } as never; -const getMock = jest.fn(() => Promise.resolve(mockDataView)); -const mockDataViewService = { - get: getMock, -} as unknown as DataView; -const services = { +setUnifiedDocViewerServices({ uiSettings: { get: (key: string) => { if (key === 'discover:useNewFieldsApi') { @@ -33,29 +28,21 @@ const services = { } }, }, - data: { - dataViewService: mockDataViewService, - }, - theme: { - theme$: of({ darkMode: false }), - }, -}; +} as UnifiedDocViewerServices); describe('Source Viewer component', () => { test('renders loading state', () => { jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [0, null, () => {}]); const comp = mountWithIntl( - - {}} - /> - + {}} + /> ); const loadingIndicator = comp.find(EuiLoadingSpinner); expect(loadingIndicator).not.toBe(null); @@ -65,16 +52,14 @@ describe('Source Viewer component', () => { jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [3, null, () => {}]); const comp = mountWithIntl( - - {}} - /> - + {}} + /> ); const errorPrompt = comp.find(EuiEmptyPrompt); expect(errorPrompt.length).toBe(1); @@ -105,16 +90,14 @@ describe('Source Viewer component', () => { return false; }); const comp = mountWithIntl( - - {}} - /> - + {}} + /> ); const jsonCodeEditor = comp.find(JsonCodeEditorCommon); expect(jsonCodeEditor).not.toBe(null); diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx index 140fbd6e08cb0..189cbd2b5be4a 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx @@ -16,7 +16,8 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { ElasticRequestState } from '@kbn/unified-doc-viewer'; import { DOC_TABLE_LEGACY, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils'; -import { useEsDocSearch, useUnifiedDocViewerServices } from '../../hooks'; +import { getUnifiedDocViewerServices } from '../../plugin'; +import { useEsDocSearch } from '../../hooks'; import { getHeight } from './get_height'; import { JSONCodeEditorCommonMemoized } from '../json_code_editor'; @@ -51,7 +52,7 @@ export const DocViewerSource = ({ const [editor, setEditor] = useState(); const [editorHeight, setEditorHeight] = useState(); const [jsonValue, setJsonValue] = useState(''); - const { uiSettings } = useUnifiedDocViewerServices(); + const { uiSettings } = getUnifiedDocViewerServices(); const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); const useDocExplorer = !uiSettings.get(DOC_TABLE_LEGACY); const [requestState, hit] = useEsDocSearch({ diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.test.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.test.tsx index cdf4b7cb9b0b7..26ad6bad42ffa 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.test.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.test.tsx @@ -12,9 +12,9 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewerLegacyTable } from './table'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { buildDataTableRecord } from '@kbn/discover-utils'; -import type { UnifiedDocViewerServices } from '../../../hooks'; +import { setUnifiedDocViewerServices } from '../../../plugin'; +import type { UnifiedDocViewerServices } from '../../../types'; const services = { uiSettings: { @@ -77,11 +77,8 @@ const mountComponent = ( props: DocViewRenderProps, overrides?: Partial ) => { - return mountWithIntl( - - {' '} - - ); + setUnifiedDocViewerServices({ ...services, ...overrides } as UnifiedDocViewerServices); + return mountWithIntl(); }; describe('DocViewTable at Discover', () => { diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx index 3d5c7e277be50..310c8653bbee5 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx @@ -18,7 +18,7 @@ import { isNestedFieldParent, } from '@kbn/discover-utils'; import type { DocViewRenderProps, FieldRecordLegacy } from '@kbn/unified-doc-viewer/types'; -import { useUnifiedDocViewerServices } from '../../../hooks'; +import { getUnifiedDocViewerServices } from '../../../plugin'; import { ACTIONS_COLUMN, MAIN_COLUMNS } from './table_columns'; export const DocViewerLegacyTable = ({ @@ -30,7 +30,7 @@ export const DocViewerLegacyTable = ({ onAddColumn, onRemoveColumn, }: DocViewRenderProps) => { - const { fieldFormats, uiSettings } = useUnifiedDocViewerServices(); + const { fieldFormats, uiSettings } = getUnifiedDocViewerServices(); const showMultiFields = useMemo(() => uiSettings.get(SHOW_MULTIFIELDS), [uiSettings]); const mapping = useCallback((name: string) => dataView.fields.getByName(name), [dataView.fields]); diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx index 4364ab5ae4ed0..5c6cbc6d80652 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx @@ -41,7 +41,7 @@ import { import { fieldNameWildcardMatcher, getFieldSearchMatchingHighlight } from '@kbn/field-utils'; import type { DocViewRenderProps, FieldRecordLegacy } from '@kbn/unified-doc-viewer/types'; import { FieldName } from '@kbn/unified-doc-viewer'; -import { useUnifiedDocViewerServices } from '../../hooks'; +import { getUnifiedDocViewerServices } from '../../plugin'; import { TableFieldValue } from './table_cell_value'; import { TableActions } from './table_cell_actions'; @@ -116,7 +116,7 @@ export const DocViewerTable = ({ }: DocViewRenderProps) => { const showActionsInsideTableCell = useIsWithinBreakpoints(['xl'], true); - const { fieldFormats, storage, uiSettings } = useUnifiedDocViewerServices(); + const { fieldFormats, storage, uiSettings } = getUnifiedDocViewerServices(); const showMultiFields = uiSettings.get(SHOW_MULTIFIELDS); const currentDataViewId = dataView.id!; diff --git a/src/plugins/unified_doc_viewer/public/hooks/index.ts b/src/plugins/unified_doc_viewer/public/hooks/index.ts index 547032ce4415e..382c8d4de305d 100644 --- a/src/plugins/unified_doc_viewer/public/hooks/index.ts +++ b/src/plugins/unified_doc_viewer/public/hooks/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export * from './use_doc_viewer_services'; export * from './use_es_doc_search'; diff --git a/src/plugins/unified_doc_viewer/public/hooks/use_doc_viewer_services.ts b/src/plugins/unified_doc_viewer/public/hooks/use_doc_viewer_services.ts deleted file mode 100644 index 4287e87ea6aa3..0000000000000 --- a/src/plugins/unified_doc_viewer/public/hooks/use_doc_viewer_services.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import type { Storage } from '@kbn/kibana-utils-plugin/public'; -import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { UnifiedDocViewerStart } from '../plugin'; - -export interface UnifiedDocViewerServices { - analytics: AnalyticsServiceStart; - data: DataPublicPluginStart; - fieldFormats: FieldFormatsStart; - storage: Storage; - uiSettings: IUiSettingsClient; - unifiedDocViewer: UnifiedDocViewerStart; -} - -export function useUnifiedDocViewerServices(): UnifiedDocViewerServices { - const { services } = useKibana(); - const { analytics, data, fieldFormats, storage, uiSettings, unifiedDocViewer } = services; - return { analytics, data, fieldFormats, storage, uiSettings, unifiedDocViewer }; -} diff --git a/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx b/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx index 640e6fd48462d..b0beac94ab4fa 100644 --- a/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx +++ b/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx @@ -15,12 +15,12 @@ import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource, buildDataTableRecord, } from '@kbn/discover-utils'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import React from 'react'; +import { setUnifiedDocViewerServices } from '../plugin'; +import { UnifiedDocViewerServices } from '../types'; const index = 'test-index'; const mockSearchResult = new Subject(); -const services = { +setUnifiedDocViewerServices({ data: { search: { search: jest.fn(() => { @@ -35,7 +35,7 @@ const services = { } }, }, -}; +} as unknown as UnifiedDocViewerServices); describe('Test of helper / hook', () => { test('buildSearchBody given useNewFieldsApi is false', () => { @@ -237,9 +237,6 @@ describe('Test of helper / hook', () => { const hook = renderHook((p: EsDocSearchProps) => useEsDocSearch(p), { initialProps: props, - wrapper: ({ children }) => ( - {children} - ), }); expect(hook.result.current.slice(0, 2)).toEqual([ElasticRequestState.Loading, null]); @@ -261,9 +258,6 @@ describe('Test of helper / hook', () => { const hook = renderHook((p: EsDocSearchProps) => useEsDocSearch(p), { initialProps: props, - wrapper: ({ children }) => ( - {children} - ), }); await act(async () => { @@ -315,9 +309,6 @@ describe('Test of helper / hook', () => { const hook = renderHook((p: EsDocSearchProps) => useEsDocSearch(p), { initialProps: props, - wrapper: ({ children }) => ( - {children} - ), }); expect(hook.result.current.slice(0, 2)).toEqual([ diff --git a/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.ts b/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.ts index 80140a1dce0ac..ef236e4a9118a 100644 --- a/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.ts +++ b/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.ts @@ -14,7 +14,7 @@ import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { SEARCH_FIELDS_FROM_SOURCE, buildDataTableRecord } from '@kbn/discover-utils'; import { ElasticRequestState } from '@kbn/unified-doc-viewer'; -import { useUnifiedDocViewerServices } from './use_doc_viewer_services'; +import { getUnifiedDocViewerServices } from '../plugin'; type RequestBody = Pick; @@ -53,7 +53,7 @@ export function useEsDocSearch({ }: EsDocSearchProps): [ElasticRequestState, DataTableRecord | null, () => void] { const [status, setStatus] = useState(ElasticRequestState.Loading); const [hit, setHit] = useState(null); - const { data, uiSettings, analytics } = useUnifiedDocViewerServices(); + const { data, uiSettings, analytics } = getUnifiedDocViewerServices(); const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); const requestData = useCallback(async () => { diff --git a/src/plugins/unified_doc_viewer/public/index.tsx b/src/plugins/unified_doc_viewer/public/index.tsx index d08de9dcaa0eb..ffe5c3f16d78c 100644 --- a/src/plugins/unified_doc_viewer/public/index.tsx +++ b/src/plugins/unified_doc_viewer/public/index.tsx @@ -34,6 +34,6 @@ export const UnifiedDocViewer = withSuspense( ); -export { useEsDocSearch, useUnifiedDocViewerServices } from './hooks'; +export { useEsDocSearch } from './hooks'; export const plugin = () => new UnifiedDocViewerPublicPlugin(); diff --git a/src/plugins/unified_doc_viewer/public/plugin.tsx b/src/plugins/unified_doc_viewer/public/plugin.tsx index 018e6ffadd312..a79ae9e44d0ca 100644 --- a/src/plugins/unified_doc_viewer/public/plugin.tsx +++ b/src/plugins/unified_doc_viewer/public/plugin.tsx @@ -16,7 +16,7 @@ import { createGetterSetter, Storage } from '@kbn/kibana-utils-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { CoreStart } from '@kbn/core/public'; -import { type UnifiedDocViewerServices, useUnifiedDocViewerServices } from './hooks'; +import type { UnifiedDocViewerServices } from './types'; export const [getUnifiedDocViewerServices, setUnifiedDocViewerServices] = createGetterSetter('UnifiedDocViewerServices'); @@ -50,8 +50,7 @@ export class UnifiedDocViewerPublicPlugin }), order: 10, component: (props) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const { uiSettings } = useUnifiedDocViewerServices(); + const { uiSettings } = getUnifiedDocViewerServices(); const DocView = uiSettings.get(DOC_TABLE_LEGACY) ? DocViewerLegacyTable : DocViewerTable; return ( diff --git a/src/plugins/unified_doc_viewer/public/types.ts b/src/plugins/unified_doc_viewer/public/types.ts index d9ec40eedfffb..5a89a6037bec8 100644 --- a/src/plugins/unified_doc_viewer/public/types.ts +++ b/src/plugins/unified_doc_viewer/public/types.ts @@ -7,5 +7,21 @@ */ export type { JsonCodeEditorProps } from './components'; -export type { EsDocSearchProps, UnifiedDocViewerServices } from './hooks'; +export type { EsDocSearchProps } from './hooks'; export type { UnifiedDocViewerSetup, UnifiedDocViewerStart } from './plugin'; + +import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import type { UnifiedDocViewerStart } from './plugin'; + +export interface UnifiedDocViewerServices { + analytics: AnalyticsServiceStart; + data: DataPublicPluginStart; + fieldFormats: FieldFormatsStart; + storage: Storage; + uiSettings: IUiSettingsClient; + unifiedDocViewer: UnifiedDocViewerStart; +} diff --git a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx index ec231d577e11a..a42a91510abec 100644 --- a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx +++ b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx @@ -23,6 +23,7 @@ import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { SortingService } from './sorting_service'; +import { MIDDLE_TRUNCATION_PROPS } from '../filter_bar/filter_editor/lib/helpers'; const strings = { sortOrder: { @@ -120,6 +121,10 @@ export function DataViewsList({ checked?: 'on' | 'off' | undefined; }> {...selectableProps} + listProps={{ + truncationProps: MIDDLE_TRUNCATION_PROPS, + ...(selectableProps?.listProps ? selectableProps.listProps : undefined), + }} data-test-subj="indexPattern-switcher" searchable singleSelection="always" diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index e41d1583341da..d17c12d8269d5 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -27,6 +27,7 @@ interface SaveDashboardOptions { } export class DashboardPageObject extends FtrService { + private readonly comboBox = this.ctx.getService('comboBox'); private readonly config = this.ctx.getService('config'); private readonly log = this.ctx.getService('log'); private readonly find = this.ctx.getService('find'); @@ -555,9 +556,9 @@ export class DashboardPageObject extends FtrService { } public async selectDashboardTags(tagNames: string[]) { - await this.testSubjects.click('savedObjectTagSelector'); + const tagsComboBox = await this.testSubjects.find('savedObjectTagSelector'); for (const tagName of tagNames) { - await this.testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); + await this.comboBox.setElement(tagsComboBox, tagName); } await this.testSubjects.click('savedObjectTitle'); } diff --git a/tsconfig.base.json b/tsconfig.base.json index 4591e22581156..4ed3f86eef5ae 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1104,6 +1104,8 @@ "@kbn/observability-alerting-test-data/*": ["x-pack/packages/observability/alerting_test_data/*"], "@kbn/observability-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/observability"], "@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"], + "@kbn/observability-get-padded-alert-time-range-util": ["x-pack/packages/observability/get_padded_alert_time_range_util"], + "@kbn/observability-get-padded-alert-time-range-util/*": ["x-pack/packages/observability/get_padded_alert_time_range_util/*"], "@kbn/observability-log-explorer-plugin": ["x-pack/plugins/observability_log_explorer"], "@kbn/observability-log-explorer-plugin/*": ["x-pack/plugins/observability_log_explorer/*"], "@kbn/observability-onboarding-plugin": ["x-pack/plugins/observability_onboarding"], diff --git a/x-pack/packages/ml/trained_models_utils/index.ts b/x-pack/packages/ml/trained_models_utils/index.ts index 0ae43c5ef4013..b9ad2e2ae4d4e 100644 --- a/x-pack/packages/ml/trained_models_utils/index.ts +++ b/x-pack/packages/ml/trained_models_utils/index.ts @@ -19,7 +19,8 @@ export { type ModelDefinition, type ModelDefinitionResponse, type ElserVersion, - type GetElserOptions, + type GetModelDownloadConfigOptions, + type ElasticCuratedModelName, ELSER_ID_V1, ELASTIC_MODEL_TAG, ELASTIC_MODEL_TYPE, diff --git a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts index 917e7b5cac3e4..10e6618672f49 100644 --- a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts +++ b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts @@ -61,6 +61,7 @@ export const ELASTIC_MODEL_DEFINITIONS: Record = Object description: i18n.translate('xpack.ml.trainedModels.modelsList.elserDescription', { defaultMessage: 'Elastic Learned Sparse EncodeR v1 (Tech Preview)', }), + type: ['elastic', 'pytorch', 'text_expansion'], }, '.elser_model_2': { modelName: 'elser', @@ -74,6 +75,7 @@ export const ELASTIC_MODEL_DEFINITIONS: Record = Object description: i18n.translate('xpack.ml.trainedModels.modelsList.elserV2Description', { defaultMessage: 'Elastic Learned Sparse EncodeR v2', }), + type: ['elastic', 'pytorch', 'text_expansion'], }, '.elser_model_2_linux-x86_64': { modelName: 'elser', @@ -88,14 +90,49 @@ export const ELASTIC_MODEL_DEFINITIONS: Record = Object description: i18n.translate('xpack.ml.trainedModels.modelsList.elserV2x86Description', { defaultMessage: 'Elastic Learned Sparse EncodeR v2, optimized for linux-x86_64', }), + type: ['elastic', 'pytorch', 'text_expansion'], + }, + '.multilingual-e5-small': { + modelName: 'e5', + version: 1, + default: true, + config: { + input: { + field_names: ['text_field'], + }, + }, + description: i18n.translate('xpack.ml.trainedModels.modelsList.e5v1Description', { + defaultMessage: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', + }), + license: 'MIT', + type: ['pytorch', 'text_embedding'], + }, + '.multilingual-e5-small_linux-x86_64': { + modelName: 'e5', + version: 1, + os: 'Linux', + arch: 'amd64', + config: { + input: { + field_names: ['text_field'], + }, + }, + description: i18n.translate('xpack.ml.trainedModels.modelsList.e5v1x86Description', { + defaultMessage: + 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', + }), + license: 'MIT', + type: ['pytorch', 'text_embedding'], }, } as const); +export type ElasticCuratedModelName = 'elser' | 'e5'; + export interface ModelDefinition { /** * Model name, e.g. elser */ - modelName: string; + modelName: ElasticCuratedModelName; version: number; /** * Default PUT model configuration @@ -107,13 +144,15 @@ export interface ModelDefinition { default?: boolean; recommended?: boolean; hidden?: boolean; + license?: string; + type?: readonly string[]; } export type ModelDefinitionResponse = ModelDefinition & { /** * Complete model id, e.g. .elser_model_2_linux-x86_64 */ - name: string; + model_id: string; }; export type ElasticModelId = keyof typeof ELASTIC_MODEL_DEFINITIONS; @@ -129,6 +168,6 @@ export type ModelState = typeof MODEL_STATE[keyof typeof MODEL_STATE] | null; export type ElserVersion = 1 | 2; -export interface GetElserOptions { +export interface GetModelDownloadConfigOptions { version?: ElserVersion; } diff --git a/x-pack/packages/observability/alert_details/index.ts b/x-pack/packages/observability/alert_details/index.ts index 6f0200e9bf72e..b90173ddf136f 100644 --- a/x-pack/packages/observability/alert_details/index.ts +++ b/x-pack/packages/observability/alert_details/index.ts @@ -9,5 +9,4 @@ export { AlertAnnotation } from './src/components/alert_annotation'; export { AlertActiveTimeRangeAnnotation } from './src/components/alert_active_time_range_annotation'; export { AlertThresholdTimeRangeRect } from './src/components/alert_threshold_time_range_rect'; export { AlertThresholdAnnotation } from './src/components/alert_threshold_annotation'; -export { getPaddedAlertTimeRange } from './src/helpers/get_padded_alert_time_range'; export { useAlertsHistory } from './src/hooks/use_alerts_history'; diff --git a/x-pack/packages/observability/alert_details/package.json b/x-pack/packages/observability/alert_details/package.json index 3baee7af3443e..2764095b1074f 100644 --- a/x-pack/packages/observability/alert_details/package.json +++ b/x-pack/packages/observability/alert_details/package.json @@ -1,7 +1,6 @@ { "name": "@kbn/observability-alert-details", "descriptio": "Helper and components related to alert details", - "author": "Actionable Observability", "private": true, "version": "1.0.0", "license": "Elastic License 2.0" diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/README.md b/x-pack/packages/observability/get_padded_alert_time_range_util/README.md new file mode 100644 index 0000000000000..049cd022eb927 --- /dev/null +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/README.md @@ -0,0 +1,3 @@ +# @kbn/get-padded-alert-time-range-util + +A utility to get padded alert time range based on alert's start and end time \ No newline at end of file diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/index.ts b/x-pack/packages/observability/get_padded_alert_time_range_util/index.ts new file mode 100644 index 0000000000000..ea4c87b42729d --- /dev/null +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { getPaddedAlertTimeRange } from './src/get_padded_alert_time_range'; diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/jest.config.js b/x-pack/packages/observability/get_padded_alert_time_range_util/jest.config.js new file mode 100644 index 0000000000000..2e476b09b3c4c --- /dev/null +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/jest.config.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/observability/get_padded_alert_time_range_util'], +}; diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc b/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc new file mode 100644 index 0000000000000..30797d6915c49 --- /dev/null +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/observability-get-padded-alert-time-range-util", + "owner": "@elastic/obs-ux-management-team" +} diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/package.json b/x-pack/packages/observability/get_padded_alert_time_range_util/package.json new file mode 100644 index 0000000000000..055cae3211ce6 --- /dev/null +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/observability-get-padded-alert-time-range-util", + "descriptio": "An util to get padded alert time range", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.test.ts b/x-pack/packages/observability/get_padded_alert_time_range_util/src/get_padded_alert_time_range.test.ts similarity index 100% rename from x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.test.ts rename to x-pack/packages/observability/get_padded_alert_time_range_util/src/get_padded_alert_time_range.test.ts diff --git a/x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.ts b/x-pack/packages/observability/get_padded_alert_time_range_util/src/get_padded_alert_time_range.ts similarity index 100% rename from x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.ts rename to x-pack/packages/observability/get_padded_alert_time_range_util/src/get_padded_alert_time_range.ts diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/tsconfig.json b/x-pack/packages/observability/get_padded_alert_time_range_util/tsconfig.json new file mode 100644 index 0000000000000..0d78dace105e1 --- /dev/null +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/x-pack/packages/security-solution/upselling/messages/index.tsx b/x-pack/packages/security-solution/upselling/messages/index.tsx index 633f44d21b770..4933ff36cfa11 100644 --- a/x-pack/packages/security-solution/upselling/messages/index.tsx +++ b/x-pack/packages/security-solution/upselling/messages/index.tsx @@ -14,3 +14,11 @@ export const UPGRADE_INVESTIGATION_GUIDE = (requiredLicense: string) => requiredLicense, }, }); + +export const UPGRADE_ALERT_ASSIGNMENTS = (requiredLicense: string) => + i18n.translate('securitySolutionPackages.alertAssignments.upsell', { + defaultMessage: 'Upgrade to {requiredLicense} to make use of alert assignments', + values: { + requiredLicense, + }, + }); diff --git a/x-pack/packages/security-solution/upselling/service/types.ts b/x-pack/packages/security-solution/upselling/service/types.ts index d14f39ac9796a..fdb66b27d97f4 100644 --- a/x-pack/packages/security-solution/upselling/service/types.ts +++ b/x-pack/packages/security-solution/upselling/service/types.ts @@ -17,4 +17,4 @@ export type UpsellingSectionId = | 'osquery_automated_response_actions' | 'ruleDetailsEndpointExceptions'; -export type UpsellingMessageId = 'investigation_guide'; +export type UpsellingMessageId = 'investigation_guide' | 'alert_assignments'; diff --git a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts index 22f91b71d4492..888a00cd5ff41 100644 --- a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts +++ b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts @@ -67,7 +67,7 @@ describe('getGenAiTokenTracking', () => { expect(logger.error).not.toHaveBeenCalled(); }); - it('should return the total, prompt, and completion token counts when given a valid Bedrock response', async () => { + it('should return the total, prompt, and completion token counts when given a valid Bedrock response for run/test subactions', async () => { const actionTypeId = '.bedrock'; const result = { @@ -103,6 +103,46 @@ describe('getGenAiTokenTracking', () => { }); }); + it('should return the total, prompt, and completion token counts when given a valid Bedrock response for invokeAI subaction', async () => { + const actionTypeId = '.bedrock'; + + const result = { + actionId: '123', + status: 'ok' as const, + data: { + message: 'Sample completion', + }, + }; + const validatedParams = { + subAction: 'invokeAI', + subActionParams: { + messages: [ + { + role: 'user', + content: 'Sample message', + }, + ], + }, + }; + const tokenTracking = await getGenAiTokenTracking({ + actionTypeId, + logger, + result, + validatedParams, + }); + + expect(tokenTracking).toEqual({ + total_tokens: 100, + prompt_tokens: 50, + completion_tokens: 50, + }); + expect(logger.error).not.toHaveBeenCalled(); + expect(mockGetTokenCountFromBedrockInvoke).toHaveBeenCalledWith({ + response: 'Sample completion', + body: '{"prompt":"Sample message"}', + }); + }); + it('should return the total, prompt, and completion token counts when given a valid OpenAI streamed response', async () => { const mockReader = new IncomingMessage(new Socket()); const actionTypeId = '.gen-ai'; diff --git a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts index 866580e8e7b3b..78dfe3ecb1728 100644 --- a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts +++ b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts @@ -117,6 +117,32 @@ export const getGenAiTokenTracking = async ({ logger.error(e); } } + + // this is a non-streamed Bedrock response used by security solution + if (actionTypeId === '.bedrock' && validatedParams.subAction === 'invokeAI') { + try { + const { total, prompt, completion } = await getTokenCountFromBedrockInvoke({ + response: ( + result.data as unknown as { + message: string; + } + ).message, + body: JSON.stringify({ + prompt: (validatedParams as { subActionParams: { messages: Array<{ content: string }> } }) + .subActionParams.messages[0].content, + }), + }); + + return { + total_tokens: total, + prompt_tokens: prompt, + completion_tokens: completion, + }; + } catch (e) { + logger.error('Failed to calculate tokens from Bedrock invoke response'); + logger.error(e); + } + } return null; }; diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index eb3132eba5413..24aa552f6a1ef 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -311,6 +311,9 @@ describe('mappingFromFieldMap', () => { workflow_tags: { type: 'keyword', }, + workflow_assignee_ids: { + type: 'keyword', + }, }, }, space_ids: { diff --git a/x-pack/plugins/alerting/common/disabled_action_groups.ts b/x-pack/plugins/alerting/common/disabled_action_groups.ts index 352fbb03524a8..b6b603c10c0f1 100644 --- a/x-pack/plugins/alerting/common/disabled_action_groups.ts +++ b/x-pack/plugins/alerting/common/disabled_action_groups.ts @@ -8,7 +8,7 @@ import { RecoveredActionGroup } from './builtin_action_groups'; const DisabledActionGroupsByActionType: Record = { - [RecoveredActionGroup.id]: ['.jira', '.servicenow', '.resilient'], + [RecoveredActionGroup.id]: ['.jira', '.resilient'], }; export const DisabledActionTypeIdsForActionGroup: Map = new Map( diff --git a/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.test.ts b/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.test.ts index cacc544a3afe8..cfdc451790bc5 100644 --- a/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.test.ts +++ b/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.test.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { getKueryWithMobileFilters } from './get_kuery_with_mobile_filters'; +import { + getKueryWithMobileFilters, + getKueryWithMobileCrashFilter, + getKueryWithMobileErrorFilter, +} from './get_kuery_with_mobile_filters'; describe('getKueryWithMobileFilters', () => { it('should handle empty and undefined values', () => { const result = getKueryWithMobileFilters({ @@ -70,3 +74,68 @@ describe('getKueryWithMobileFilters', () => { ); }); }); + +describe('getKueryWithMobileCrashFilter', () => { + it('should handle empty and undefined values', () => { + const result = getKueryWithMobileCrashFilter({ + groupId: undefined, + kuery: '', + }); + expect(result).toBe('error.type: crash'); + }); + it('should return kuery and crash filter when groupId is empty', () => { + const result = getKueryWithMobileCrashFilter({ + groupId: undefined, + kuery: 'foo.bar: test', + }); + expect(result).toBe('foo.bar: test and error.type: crash'); + }); + it('should return crash filter and groupId when kuery is empty', () => { + const result = getKueryWithMobileCrashFilter({ + groupId: '1', + kuery: '', + }); + expect(result).toBe('error.type: crash and error.grouping_key: 1'); + }); + it('should return crash filter, groupId, and kuery in kql format', () => { + const result = getKueryWithMobileCrashFilter({ + groupId: '1', + kuery: 'foo.bar: test', + }); + expect(result).toBe( + 'foo.bar: test and error.type: crash and error.grouping_key: 1' + ); + }); +}); +describe('getKueryWithMobileErrorFilter', () => { + it('should handle empty and undefined values', () => { + const result = getKueryWithMobileErrorFilter({ + groupId: undefined, + kuery: '', + }); + expect(result).toBe('NOT error.type: crash'); + }); + it('should return kuery and error filter when groupId is empty', () => { + const result = getKueryWithMobileErrorFilter({ + kuery: 'foo.bar: test', + groupId: undefined, + }); + expect(result).toBe('foo.bar: test and NOT error.type: crash'); + }); + it('should return error filter and groupId when kuery is empty', () => { + const result = getKueryWithMobileErrorFilter({ + groupId: '1', + kuery: '', + }); + expect(result).toBe('NOT error.type: crash and error.grouping_key: 1'); + }); + it('should return error filter, groupId, and kuery in kql format', () => { + const result = getKueryWithMobileErrorFilter({ + groupId: '1', + kuery: 'foo.bar: test', + }); + expect(result).toBe( + 'foo.bar: test and NOT error.type: crash and error.grouping_key: 1' + ); + }); +}); diff --git a/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.ts b/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.ts index 19970a0b24b93..2efaf1e2e8580 100644 --- a/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.ts +++ b/x-pack/plugins/apm/common/utils/get_kuery_with_mobile_filters.ts @@ -10,6 +10,8 @@ import { DEVICE_MODEL_IDENTIFIER, NETWORK_CONNECTION_TYPE, SERVICE_VERSION, + ERROR_TYPE, + ERROR_GROUP_ID, } from '../es_fields/apm'; import { fieldValuePairToKql } from './field_value_pair_to_kql'; @@ -38,3 +40,37 @@ export function getKueryWithMobileFilters({ return kueryWithFilters; } + +export function getKueryWithMobileCrashFilter({ + groupId, + kuery, +}: { + groupId: string | undefined; + kuery: string; +}) { + const kueryWithFilters = [ + kuery, + ...fieldValuePairToKql(ERROR_TYPE, 'crash'), + ...fieldValuePairToKql(ERROR_GROUP_ID, groupId), + ] + .filter(Boolean) + .join(' and '); + return kueryWithFilters; +} + +export function getKueryWithMobileErrorFilter({ + groupId, + kuery, +}: { + groupId: string | undefined; + kuery: string; +}) { + const kueryWithFilters = [ + kuery, + `NOT ${ERROR_TYPE}: crash`, + ...fieldValuePairToKql(ERROR_GROUP_ID, groupId), + ] + .filter(Boolean) + .join(' and '); + return kueryWithFilters; +} diff --git a/x-pack/plugins/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx b/x-pack/plugins/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx index 91fa325f4ae7e..f8b14acfb364a 100644 --- a/x-pack/plugins/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx @@ -19,7 +19,7 @@ import { import moment from 'moment'; import React, { useEffect, useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { getPaddedAlertTimeRange } from '@kbn/observability-alert-details'; +import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import { EuiCallOut } from '@elastic/eui'; import { SERVICE_ENVIRONMENT } from '../../../../../common/es_fields/apm'; import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx index 574e9db2f507b..d8f500f9fb78a 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx @@ -34,7 +34,7 @@ import { TraceSearchType } from '../../../../../common/trace_explorer'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; import { useApmRouter } from '../../../../hooks/use_apm_router'; import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher'; import { useTraceExplorerEnabledSetting } from '../../../../hooks/use_trace_explorer_enabled_setting'; @@ -102,7 +102,11 @@ export function ErrorSampleDetails({ const { path: { groupId }, query, - } = useApmParams('/services/{serviceName}/errors/{groupId}'); + } = useAnyOfApmParams( + '/services/{serviceName}/errors/{groupId}', + '/mobile-services/{serviceName}/errors-and-crashes/errors/{groupId}', + '/mobile-services/{serviceName}/errors-and-crashes/crashes/{groupId}' + ); const { kuery } = query; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/index.tsx index e5b13aa0df213..48cd78e6705d0 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/error_sampler/index.tsx @@ -8,7 +8,7 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; -import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; import { FETCH_STATUS, isPending, @@ -36,7 +36,11 @@ export function ErrorSampler({ const { path: { groupId }, query, - } = useApmParams('/services/{serviceName}/errors/{groupId}'); + } = useAnyOfApmParams( + '/services/{serviceName}/errors/{groupId}', + '/mobile-services/{serviceName}/errors-and-crashes/errors/{groupId}', + '/mobile-services/{serviceName}/errors-and-crashes/crashes/{groupId}' + ); const { rangeFrom, rangeTo, environment, kuery, errorId } = query; diff --git a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_errors_and_crashes_treemap/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_errors_and_crashes_treemap/index.tsx new file mode 100644 index 0000000000000..a30495b703eb7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_errors_and_crashes_treemap/index.tsx @@ -0,0 +1,79 @@ +/* + * 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 { EuiSpacer } from '@elastic/eui'; +import { TreemapSelect, TreemapTypes } from './treemap_select'; +import { TreemapChart } from '../../../../shared/charts/treemap_chart'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; +import { + DEVICE_MODEL_IDENTIFIER, + SERVICE_VERSION, +} from '../../../../../../common/es_fields/apm'; + +const ES_FIELD_MAPPING: Record = { + [TreemapTypes.Devices]: DEVICE_MODEL_IDENTIFIER, + [TreemapTypes.Versions]: SERVICE_VERSION, +}; + +export function MobileErrorsAndCrashesTreemap({ + kuery, + serviceName, + start, + end, + environment, +}: { + kuery: string; + serviceName: string; + start: string; + end: string; + environment: string; +}) { + const [selectedTreemap, selectTreemap] = useState(TreemapTypes.Devices); + + const { data, status } = useFetcher( + (callApmApi) => { + const fieldName = ES_FIELD_MAPPING[selectedTreemap]; + if (fieldName) { + return callApmApi( + 'GET /internal/apm/mobile-services/{serviceName}/error_terms', + { + params: { + path: { + serviceName, + }, + query: { + environment, + kuery, + start, + end, + fieldName, + size: 500, + }, + }, + } + ); + } + }, + [environment, kuery, serviceName, start, end, selectedTreemap] + ); + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_errors_and_crashes_treemap/treemap_select.tsx b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_errors_and_crashes_treemap/treemap_select.tsx new file mode 100644 index 0000000000000..498e0ef8fda7c --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_errors_and_crashes_treemap/treemap_select.tsx @@ -0,0 +1,121 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; +import type { EuiSuperSelectOption } from '@elastic/eui'; + +export enum TreemapTypes { + Devices = 'devices', + Versions = 'versions', +} + +const options: Array> = [ + { + value: TreemapTypes.Devices, + label: i18n.translate( + 'xpack.apm.transactionOverview.treemap.dropdown.devices', + { + defaultMessage: 'Devices', + } + ), + description: i18n.translate( + 'xpack.apm.errorOverview.treemap.dropdown.devices.subtitle', + { + defaultMessage: + 'This treemap view allows for easy and faster visual way the most affected devices', + } + ), + }, + { + value: TreemapTypes.Versions, + label: i18n.translate( + 'xpack.apm.transactionOverview.treemap.versions.devices', + { + defaultMessage: 'Versions', + } + ), + description: i18n.translate( + 'xpack.apm.errorOverview.treemap.dropdown.versions.subtitle', + { + defaultMessage: + 'This treemap view allows for easy and faster visual way the most affected versions.', + } + ), + }, +].map(({ value, label, description }) => ({ + inputDisplay: label, + value, + dropdownDisplay: ( + <> + {label} + +

{description}

+
+ + ), +})); + +export function TreemapSelect({ + selectedTreemap, + onChange, +}: { + selectedTreemap: TreemapTypes; + onChange: (value: TreemapTypes) => void; +}) { + const currentTreemap = + options.find(({ value }) => value === selectedTreemap) ?? options[0]; + + return ( + + + +

+ {i18n.translate('xpack.apm.errorOverview.treemap.title', { + defaultMessage: 'Most affected {currentTreemap}', + values: { currentTreemap: currentTreemap.value }, + })} +

+
+ + {i18n.translate('xpack.apm.errorOverview.treemap.subtitle', { + defaultMessage: + 'Treemap showing the total and most affected {currentTreemap}', + values: { currentTreemap: currentTreemap.value }, + })} + +
+ + + + + {i18n.translate('xpack.apm.transactionOverview.treemap.show', { + defaultMessage: 'Show', + })} + + + + + + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_http_error_rate/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_http_error_rate/index.tsx new file mode 100644 index 0000000000000..f26fe41a0ecab --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_http_error_rate/index.tsx @@ -0,0 +1,138 @@ +/* + * 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 { + EuiPanel, + EuiTitle, + EuiIconTip, + EuiFlexItem, + EuiFlexGroup, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { getComparisonChartTheme } from '../../../../shared/time_comparison/get_comparison_chart_theme'; +import { TimeseriesChartWithContext } from '../../../../shared/charts/timeseries_chart_with_context'; + +import { useFetcher } from '../../../../../hooks/use_fetcher'; + +import { + ChartType, + getTimeSeriesColor, +} from '../../../../shared/charts/helper/get_timeseries_color'; +import { usePreviousPeriodLabel } from '../../../../../hooks/use_previous_period_text'; + +const INITIAL_STATE = { + currentPeriod: { timeseries: [] }, + previousPeriod: { timeseries: [] }, +}; + +export function HttpErrorRateChart({ + height, + kuery, + serviceName, + start, + end, + environment, + offset, + comparisonEnabled, +}: { + height: number; + kuery: string; + serviceName: string; + start: string; + end: string; + environment: string; + offset?: string; + comparisonEnabled: boolean; +}) { + const comparisonChartTheme = getComparisonChartTheme(); + const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( + ChartType.HTTP_REQUESTS + ); + const { data = INITIAL_STATE, status } = useFetcher( + (callApmApi) => { + return callApmApi( + 'GET /internal/apm/mobile-services/{serviceName}/error/http_error_rate', + { + params: { + path: { + serviceName, + }, + query: { + environment, + kuery, + start, + end, + offset: comparisonEnabled ? offset : undefined, + }, + }, + } + ); + }, + [environment, kuery, serviceName, start, end, offset, comparisonEnabled] + ); + + const previousPeriodLabel = usePreviousPeriodLabel(); + + const timeseries = [ + { + data: data.currentPeriod.timeseries, + type: 'linemark', + color: currentPeriodColor, + title: i18n.translate('xpack.apm.errors.httpErrorRateTitle', { + defaultMessage: 'HTTP error rate', + }), + }, + ...(comparisonEnabled + ? [ + { + data: data.previousPeriod.timeseries, + type: 'area', + color: previousPeriodColor, + title: previousPeriodLabel, + }, + ] + : []), + ]; + + return ( + + + + +

+ {i18n.translate('xpack.apm.mobile.errors.httpErrorRate', { + defaultMessage: 'HTTP Error Rate', + })} +

+
+
+ + + +
+ + `${y}`} + /> +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_most_affected/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_most_affected/index.tsx new file mode 100644 index 0000000000000..8f53f84c19b08 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_most_affected/index.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { TreemapSelect, TreemapTypes } from './treemap_select'; +import { TreemapChart } from '../../../../shared/charts/treemap_chart'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; +import { + DEVICE_MODEL_IDENTIFIER, + HOST_OS_VERSION, + SERVICE_VERSION, +} from '../../../../../../common/es_fields/apm'; + +const ES_FIELD_MAPPING: Record = { + [TreemapTypes.Devices]: DEVICE_MODEL_IDENTIFIER, + [TreemapTypes.AppVersions]: SERVICE_VERSION, + [TreemapTypes.OsVersions]: HOST_OS_VERSION, +}; + +export function MobileTreemap({ + kuery, + serviceName, + start, + end, + environment, +}: { + kuery: string; + serviceName: string; + start: string; + end: string; + environment: string; +}) { + const [selectedTreemap, selectTreemap] = useState(TreemapTypes.Devices); + + const { data, status } = useFetcher( + (callApmApi) => { + const fieldName = ES_FIELD_MAPPING[selectedTreemap]; + if (fieldName) { + return callApmApi( + 'GET /internal/apm/mobile-services/{serviceName}/terms', + { + params: { + path: { + serviceName, + }, + query: { + environment, + kuery, + start, + end, + fieldName, + size: 500, + }, + }, + } + ); + } + }, + [environment, kuery, serviceName, start, end, selectedTreemap] + ); + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_most_affected/treemap_select.tsx b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_most_affected/treemap_select.tsx new file mode 100644 index 0000000000000..b166c81b7ee13 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_most_affected/treemap_select.tsx @@ -0,0 +1,122 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; +import type { EuiSuperSelectOption } from '@elastic/eui'; + +export enum TreemapTypes { + OsVersions = 'osVersions', + AppVersions = 'appVersions', + Devices = 'devices', +} + +const options: Array> = [ + { + value: TreemapTypes.Devices, + label: i18n.translate( + 'xpack.apm.mobile.errorOverview.treemap.dropdown.devices', + { + defaultMessage: 'Devices', + } + ), + description: i18n.translate( + 'xpack.apm.mobile.errorOverview.treemap.dropdown.devices.subtitle', + { + defaultMessage: 'Treemap displaying the most affected devices.', + } + ), + }, + { + value: TreemapTypes.AppVersions, + label: i18n.translate( + 'xpack.apm.mobile.errorOverview.treemap.versions.devices', + { + defaultMessage: 'App versions', + } + ), + description: i18n.translate( + 'xpack.apm.mobile.errorOverview.treemap.dropdown.versions.subtitle', + { + defaultMessage: + 'Treemap displaying the most affected application versions.', + } + ), + }, + { + value: TreemapTypes.OsVersions, + label: i18n.translate( + 'xpack.apm.mobile.errorOverview.treemap.dropdown.osVersions', + { + defaultMessage: 'OS versions', + } + ), + description: i18n.translate( + 'xpack.apm.mobile.errorOverview.treemap.dropdown.osVersions.subtitle', + { + defaultMessage: 'Treemap displaying the most affected OS versions.', + } + ), + }, +].map(({ value, label, description }) => ({ + inputDisplay: label, + value, + dropdownDisplay: ( + <> + {label} + +

{description}

+
+ + ), +})); + +export function TreemapSelect({ + selectedTreemap, + onChange, +}: { + selectedTreemap: TreemapTypes; + onChange: (value: TreemapTypes) => void; +}) { + const currentTreemap = + options.find(({ value }) => value === selectedTreemap) ?? options[0]; + + return ( + + + +

+ {i18n.translate('xpack.apm.errorsOverview.treemap.title', { + defaultMessage: 'Most affected {currentTreemap}', + values: { currentTreemap: currentTreemap.value }, + })} +

+
+
+ + + + + + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_treemap/treemap_select.tsx b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_treemap/treemap_select.tsx index 6f86b5a83c30c..281ea1b2c2b97 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_treemap/treemap_select.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/charts/mobile_treemap/treemap_select.tsx @@ -26,7 +26,7 @@ const options: Array> = [ label: i18n.translate( 'xpack.apm.transactionOverview.treemap.dropdown.devices', { - defaultMessage: 'Devices treemap', + defaultMessage: 'Devices', } ), description: i18n.translate( @@ -42,7 +42,7 @@ const options: Array> = [ label: i18n.translate( 'xpack.apm.transactionOverview.treemap.versions.devices', { - defaultMessage: 'Versions treemap', + defaultMessage: 'Versions', } ), description: i18n.translate( diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/crash_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/crash_group_details/index.tsx new file mode 100644 index 0000000000000..4c80fe922973e --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/crash_group_details/index.tsx @@ -0,0 +1,274 @@ +/* + * 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 { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import { omit } from 'lodash'; +import { useHistory } from 'react-router-dom'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; +import { useApmServiceContext } from '../../../../../context/apm_service/use_apm_service_context'; +import { useBreadcrumb } from '../../../../../context/breadcrumbs/use_breadcrumb'; +import { useApmParams } from '../../../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../../../hooks/use_apm_router'; +import { useCrashGroupDistributionFetcher } from '../../../../../hooks/use_crash_group_distribution_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../../hooks/use_time_range'; +import type { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; +import { ErrorSampler } from '../../../error_group_details/error_sampler'; +import { ErrorDistribution } from '../shared/distribution'; +import { ChartPointerEventContextProvider } from '../../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { MobileErrorsAndCrashesTreemap } from '../../charts/mobile_errors_and_crashes_treemap'; +import { maybe } from '../../../../../../common/utils/maybe'; +import { fromQuery, toQuery } from '../../../../shared/links/url_helpers'; +import { + getKueryWithMobileCrashFilter, + getKueryWithMobileFilters, +} from '../../../../../../common/utils/get_kuery_with_mobile_filters'; + +type ErrorSamplesAPIResponse = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>; + +const emptyErrorSamples: ErrorSamplesAPIResponse = { + errorSampleIds: [], + occurrencesCount: 0, +}; + +function getShortGroupId(errorGroupId?: string) { + if (!errorGroupId) { + return NOT_AVAILABLE_LABEL; + } + + return errorGroupId.slice(0, 5); +} + +function CrashGroupHeader({ + groupId, + occurrencesCount, +}: { + groupId: string; + occurrencesCount?: number; +}) { + return ( + + + +

+ {i18n.translate('xpack.apm.CrashGroupDetails.CrashGroupTitle', { + defaultMessage: 'Crash group {errorGroupId}', + values: { + errorGroupId: getShortGroupId(groupId), + }, + })} +

+
+
+ + + {i18n.translate('xpack.apm.errorGroupDetails.occurrencesLabel', { + defaultMessage: '{occurrencesCount} occ', + values: { occurrencesCount }, + })} + + +
+ ); +} + +export function CrashGroupDetails() { + const { serviceName } = useApmServiceContext(); + + const apmRouter = useApmRouter(); + const history = useHistory(); + + const { + path: { groupId }, + query: { + rangeFrom, + rangeTo, + environment, + kuery, + serviceGroup, + comparisonEnabled, + errorId, + device, + osVersion, + appVersion, + netConnectionType, + }, + } = useApmParams( + '/mobile-services/{serviceName}/errors-and-crashes/crashes/{groupId}' + ); + + const kueryWithMobileFilters = getKueryWithMobileFilters({ + device, + osVersion, + appVersion, + netConnectionType, + kuery, + }); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + useBreadcrumb( + () => ({ + title: groupId, + href: apmRouter.link( + '/mobile-services/{serviceName}/errors-and-crashes/crashes/{groupId}', + { + path: { + serviceName, + groupId, + }, + query: { + rangeFrom, + rangeTo, + environment, + kuery: kueryWithMobileFilters, + serviceGroup, + comparisonEnabled, + }, + } + ), + }), + [ + apmRouter, + comparisonEnabled, + environment, + groupId, + kueryWithMobileFilters, + rangeFrom, + rangeTo, + serviceGroup, + serviceName, + ] + ); + + const kueryForTreemap = getKueryWithMobileCrashFilter({ + kuery: kueryWithMobileFilters, + groupId, + }); + + const { + data: errorSamplesData = emptyErrorSamples, + status: errorSamplesFetchStatus, + } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi( + 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples', + { + params: { + path: { + serviceName, + groupId, + }, + query: { + environment, + kuery: kueryWithMobileFilters, + start, + end, + }, + }, + } + ); + } + }, + [environment, kueryWithMobileFilters, serviceName, start, end, groupId] + ); + + const { crashDistributionData, status: crashDistributionStatus } = + useCrashGroupDistributionFetcher({ + serviceName, + groupId, + environment, + kuery: kueryWithMobileFilters, + }); + + useEffect(() => { + const selectedSample = errorSamplesData?.errorSampleIds.find( + (sample) => sample === errorId + ); + + if (errorSamplesFetchStatus === FETCH_STATUS.SUCCESS && !selectedSample) { + // selected sample was not found. select a new one: + const selectedErrorId = maybe(errorSamplesData?.errorSampleIds[0]); + + history.replace({ + ...history.location, + search: fromQuery({ + ...omit(toQuery(history.location.search), ['errorId']), + errorId: selectedErrorId, + }), + }); + } + }, [history, errorId, errorSamplesData, errorSamplesFetchStatus]); + + // If there are 0 occurrences, show only charts w. empty message + const showDetails = errorSamplesData.occurrencesCount !== 0; + + return ( + <> + + + + + + + + + + + + + + + + + + + + + {showDetails && ( + + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/error_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/error_group_details/index.tsx new file mode 100644 index 0000000000000..eb71f5c04ea37 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/error_group_details/index.tsx @@ -0,0 +1,275 @@ +/* + * 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 { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import { omit } from 'lodash'; +import { useHistory } from 'react-router-dom'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; +import { useApmServiceContext } from '../../../../../context/apm_service/use_apm_service_context'; +import { useBreadcrumb } from '../../../../../context/breadcrumbs/use_breadcrumb'; +import { useApmParams } from '../../../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../../../hooks/use_apm_router'; +import { useErrorGroupDistributionFetcher } from '../../../../../hooks/use_error_group_distribution_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../../hooks/use_time_range'; +import type { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; +import { ErrorSampler } from '../../../error_group_details/error_sampler'; +import { ErrorDistribution } from '../shared/distribution'; +import { ChartPointerEventContextProvider } from '../../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { MobileErrorsAndCrashesTreemap } from '../../charts/mobile_errors_and_crashes_treemap'; +import { maybe } from '../../../../../../common/utils/maybe'; +import { fromQuery, toQuery } from '../../../../shared/links/url_helpers'; +import { + getKueryWithMobileFilters, + getKueryWithMobileErrorFilter, +} from '../../../../../../common/utils/get_kuery_with_mobile_filters'; + +type ErrorSamplesAPIResponse = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>; + +const emptyErrorSamples: ErrorSamplesAPIResponse = { + errorSampleIds: [], + occurrencesCount: 0, +}; + +function getShortGroupId(errorGroupId?: string) { + if (!errorGroupId) { + return NOT_AVAILABLE_LABEL; + } + + return errorGroupId.slice(0, 5); +} + +function ErrorGroupHeader({ + groupId, + occurrencesCount, +}: { + groupId: string; + occurrencesCount?: number; +}) { + return ( + + + +

+ {i18n.translate('xpack.apm.errorGroupDetails.errorGroupTitle', { + defaultMessage: 'Error group {errorGroupId}', + values: { + errorGroupId: getShortGroupId(groupId), + }, + })} +

+
+
+ + + {i18n.translate('xpack.apm.errorGroupDetails.occurrencesLabel', { + defaultMessage: '{occurrencesCount} occ', + values: { occurrencesCount }, + })} + + +
+ ); +} + +export function ErrorGroupDetails() { + const { serviceName } = useApmServiceContext(); + + const apmRouter = useApmRouter(); + const history = useHistory(); + + const { + path: { groupId }, + query: { + rangeFrom, + rangeTo, + environment, + kuery, + serviceGroup, + comparisonEnabled, + errorId, + device, + osVersion, + appVersion, + netConnectionType, + }, + } = useApmParams( + '/mobile-services/{serviceName}/errors-and-crashes/errors/{groupId}' + ); + const kueryWithMobileFilters = getKueryWithMobileFilters({ + device, + osVersion, + appVersion, + netConnectionType, + kuery, + }); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + useBreadcrumb( + () => ({ + title: groupId, + href: apmRouter.link( + '/mobile-services/{serviceName}/errors-and-crashes/errors/{groupId}', + { + path: { + serviceName, + groupId, + }, + query: { + rangeFrom, + rangeTo, + environment, + kuery: kueryWithMobileFilters, + serviceGroup, + comparisonEnabled, + }, + } + ), + }), + [ + apmRouter, + comparisonEnabled, + environment, + groupId, + kueryWithMobileFilters, + rangeFrom, + rangeTo, + serviceGroup, + serviceName, + ] + ); + + const { + data: errorSamplesData = emptyErrorSamples, + status: errorSamplesFetchStatus, + } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi( + 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples', + { + params: { + path: { + serviceName, + groupId, + }, + query: { + environment, + kuery: kueryWithMobileFilters, + start, + end, + }, + }, + } + ); + } + }, + [environment, kueryWithMobileFilters, serviceName, start, end, groupId] + ); + + const { errorDistributionData, status: errorDistributionStatus } = + useErrorGroupDistributionFetcher({ + serviceName, + groupId, + environment, + kuery: kueryWithMobileFilters, + }); + + useEffect(() => { + const selectedSample = errorSamplesData?.errorSampleIds.find( + (sample) => sample === errorId + ); + + if (errorSamplesFetchStatus === FETCH_STATUS.SUCCESS && !selectedSample) { + // selected sample was not found. select a new one: + const selectedErrorId = maybe(errorSamplesData?.errorSampleIds[0]); + + history.replace({ + ...history.location, + search: fromQuery({ + ...omit(toQuery(history.location.search), ['errorId']), + errorId: selectedErrorId, + }), + }); + } + }, [history, errorId, errorSamplesData, errorSamplesFetchStatus]); + + // If there are 0 occurrences, show only charts w. empty message + const showDetails = errorSamplesData.occurrencesCount !== 0; + + const kueryForTreemap = getKueryWithMobileErrorFilter({ + groupId, + kuery: kueryWithMobileFilters, + }); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + {showDetails && ( + + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/shared/distribution/index.stories.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/shared/distribution/index.stories.tsx new file mode 100644 index 0000000000000..1fd2b4e7522b7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/shared/distribution/index.stories.tsx @@ -0,0 +1,93 @@ +/* + * 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, { ComponentType } from 'react'; +import { ErrorDistribution } from '.'; +import { MockApmPluginStorybook } from '../../../../../../context/apm_plugin/mock_apm_plugin_storybook'; +import { FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; + +export default { + title: 'app/ErrorGroupDetails/distribution', + component: ErrorDistribution, + decorators: [ + (Story: ComponentType) => { + return ( + + + + ); + }, + ], +}; + +export function Example() { + const distribution = { + bucketSize: 62350, + currentPeriod: [ + { x: 1624279912350, y: 6 }, + { x: 1624279974700, y: 1 }, + { x: 1624280037050, y: 2 }, + { x: 1624280099400, y: 3 }, + { x: 1624280161750, y: 13 }, + { x: 1624280224100, y: 1 }, + { x: 1624280286450, y: 2 }, + { x: 1624280348800, y: 0 }, + { x: 1624280411150, y: 4 }, + { x: 1624280473500, y: 4 }, + { x: 1624280535850, y: 1 }, + { x: 1624280598200, y: 4 }, + { x: 1624280660550, y: 0 }, + { x: 1624280722900, y: 2 }, + { x: 1624280785250, y: 3 }, + { x: 1624280847600, y: 0 }, + ], + previousPeriod: [ + { x: 1624279912350, y: 6 }, + { x: 1624279974700, y: 1 }, + { x: 1624280037050, y: 2 }, + { x: 1624280099400, y: 3 }, + { x: 1624280161750, y: 13 }, + { x: 1624280224100, y: 1 }, + { x: 1624280286450, y: 2 }, + { x: 1624280348800, y: 0 }, + { x: 1624280411150, y: 4 }, + { x: 1624280473500, y: 4 }, + { x: 1624280535850, y: 1 }, + { x: 1624280598200, y: 4 }, + { x: 1624280660550, y: 0 }, + { x: 1624280722900, y: 2 }, + { x: 1624280785250, y: 3 }, + { x: 1624280847600, y: 0 }, + ], + }; + + return ( + + ); +} + +export function EmptyState() { + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/shared/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/shared/distribution/index.tsx new file mode 100644 index 0000000000000..78e39d3f2f8f6 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_group_details/shared/distribution/index.tsx @@ -0,0 +1,92 @@ +/* + * 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 { EuiTitle, EuiIconTip, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import React from 'react'; +import { TimeseriesChartWithContext } from '../../../../../shared/charts/timeseries_chart_with_context'; +import { useLegacyUrlParams } from '../../../../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; +import { usePreviousPeriodLabel } from '../../../../../../hooks/use_previous_period_text'; +import { APIReturnType } from '../../../../../../services/rest/create_call_apm_api'; +import { getComparisonChartTheme } from '../../../../../shared/time_comparison/get_comparison_chart_theme'; + +import { + ChartType, + getTimeSeriesColor, +} from '../../../../../shared/charts/helper/get_timeseries_color'; + +type ErrorDistributionAPIResponse = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>; + +interface Props { + fetchStatus: FETCH_STATUS; + distribution?: ErrorDistributionAPIResponse; + title: string; + tip: string; + height: number; +} + +export function ErrorDistribution({ + distribution, + title, + tip, + height, + fetchStatus, +}: Props) { + const { urlParams } = useLegacyUrlParams(); + const { comparisonEnabled } = urlParams; + + const previousPeriodLabel = usePreviousPeriodLabel(); + const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( + ChartType.ERROR_OCCURRENCES + ); + const timeseries = [ + { + data: distribution?.currentPeriod ?? [], + type: 'linemark', + color: currentPeriodColor, + title, + }, + ...(comparisonEnabled + ? [ + { + data: distribution?.previousPeriod ?? [], + type: 'area', + color: previousPeriodColor, + title: previousPeriodLabel, + }, + ] + : []), + ]; + + const comparisonChartTheme = getComparisonChartTheme(); + + return ( + <> + + + +

{title}

+
+
+ + + +
+ + `${value}`} + timeseries={timeseries} + customTheme={comparisonChartTheme} + /> + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/crash_group_list.stories.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/crash_group_list.stories.tsx new file mode 100644 index 0000000000000..993d291a7e4c3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/crash_group_list.stories.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta, Story } from '@storybook/react'; +import React, { ComponentProps } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../../context/url_params_context/mock_url_params_context_provider'; + +import { MobileCrashGroupList } from '.'; + +type Args = ComponentProps; + +const stories: Meta = { + title: 'app/CrashGroupOverview/MobileCrashGroupList', + component: MobileCrashGroupList, + decorators: [ + (StoryComponent) => { + return ( + + + + + + + + ); + }, + ], +}; +export default stories; + +export const Example: Story = (args) => { + return ; +}; +Example.args = { + mainStatistics: [ + { + name: 'net/http: abort Handler', + occurrences: 14, + culprit: 'Main.func2', + groupId: '83a653297ec29afed264d7b60d5cda7b', + lastSeen: 1634833121434, + handled: false, + type: 'errorString', + }, + { + name: 'POST /api/orders (500)', + occurrences: 5, + culprit: 'logrusMiddleware', + groupId: '7a640436a9be648fd708703d1ac84650', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + { + name: 'write tcp 10.36.2.24:3000->10.36.1.14:34232: write: connection reset by peer', + occurrences: 4, + culprit: 'apiHandlers.getProductCustomers', + groupId: '95ca0e312c109aa11e298bcf07f1445b', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + { + name: 'write tcp 10.36.0.21:3000->10.36.1.252:57070: write: connection reset by peer', + occurrences: 3, + culprit: 'apiHandlers.getCustomers', + groupId: '4053d7e33d2b716c819bd96d9d6121a2', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + { + name: 'write tcp 10.36.0.21:3000->10.36.0.88:33926: write: broken pipe', + occurrences: 2, + culprit: 'apiHandlers.getOrders', + groupId: '94f4ca8ec8c02e5318cf03f46ae4c1f3', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + ], + serviceName: 'test service', +}; + +export const EmptyState: Story = (args) => { + return ; +}; +EmptyState.args = { + mainStatistics: [], + serviceName: 'test service', +}; diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/crash_group_list.test.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/crash_group_list.test.tsx new file mode 100644 index 0000000000000..cd1d4110e32fc --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/crash_group_list.test.tsx @@ -0,0 +1,19 @@ +/* + * 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 { composeStories } from '@storybook/testing-react'; +import { render } from '@testing-library/react'; +import React from 'react'; +import * as stories from './crash_group_list.stories'; + +const { Example } = composeStories(stories); + +describe('MobileCrashGroupList', () => { + it('renders', () => { + expect(() => render()).not.toThrowError(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/index.tsx new file mode 100644 index 0000000000000..2010225591fdb --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crash_group_list/index.tsx @@ -0,0 +1,204 @@ +/* + * 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 { EuiToolTip, RIGHT_ALIGNMENT, LEFT_ALIGNMENT } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import React, { useMemo } from 'react'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; +import { asInteger } from '../../../../../../common/utils/formatters'; +import { useApmParams } from '../../../../../hooks/use_apm_params'; +import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; +import { truncate } from '../../../../../utils/style'; +import { + ChartType, + getTimeSeriesColor, +} from '../../../../shared/charts/helper/get_timeseries_color'; +import { SparkPlot } from '../../../../shared/charts/spark_plot'; +import { CrashDetailLink } from '../../../../shared/links/apm/mobile/crash_detail_link'; +import { ErrorOverviewLink } from '../../../../shared/links/apm/mobile/error_overview_link'; +import { ITableColumn, ManagedTable } from '../../../../shared/managed_table'; +import { TimestampTooltip } from '../../../../shared/timestamp_tooltip'; +import { isTimeComparison } from '../../../../shared/time_comparison/get_comparison_options'; + +const MessageAndCulpritCell = euiStyled.div` + ${truncate('100%')}; +`; + +const ErrorLink = euiStyled(ErrorOverviewLink)` + ${truncate('100%')}; +`; + +type ErrorGroupItem = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics'>['errorGroups'][0]; +type ErrorGroupDetailedStatistics = + APIReturnType<'POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics'>; + +interface Props { + mainStatistics: ErrorGroupItem[]; + serviceName: string; + detailedStatisticsLoading: boolean; + detailedStatistics: ErrorGroupDetailedStatistics; + initialSortField: string; + initialSortDirection: 'asc' | 'desc'; + comparisonEnabled?: boolean; + isLoading: boolean; +} + +function MobileCrashGroupList({ + mainStatistics, + serviceName, + detailedStatisticsLoading, + detailedStatistics, + comparisonEnabled, + initialSortField, + initialSortDirection, + isLoading, +}: Props) { + const { query } = useApmParams( + '/mobile-services/{serviceName}/errors-and-crashes' + ); + const { offset } = query; + const columns = useMemo(() => { + return [ + { + name: i18n.translate('xpack.apm.errorsTable.typeColumnLabel', { + defaultMessage: 'Type', + }), + field: 'type', + sortable: false, + render: (_, { type }) => { + return ( + + {type} + + ); + }, + }, + { + name: i18n.translate( + 'xpack.apm.crashTable.crashMessageAndCulpritColumnLabel', + { + defaultMessage: 'Crash message', + } + ), + field: 'message', + sortable: false, + width: '30%', + render: (_, item: ErrorGroupItem) => { + return ( + + + + {item.name || NOT_AVAILABLE_LABEL} + + + + ); + }, + }, + { + field: 'lastSeen', + sortable: true, + name: i18n.translate('xpack.apm.errorsTable.lastSeenColumnLabel', { + defaultMessage: 'Last seen', + }), + align: LEFT_ALIGNMENT, + render: (_, { lastSeen }) => + lastSeen ? ( + + ) : ( + NOT_AVAILABLE_LABEL + ), + }, + { + field: 'occurrences', + name: i18n.translate('xpack.apm.errorsTable.occurrencesColumnLabel', { + defaultMessage: 'Occurrences', + }), + sortable: true, + dataType: 'number', + align: RIGHT_ALIGNMENT, + render: (_, { occurrences, groupId }) => { + const currentPeriodTimeseries = + detailedStatistics?.currentPeriod?.[groupId]?.timeseries; + const previousPeriodTimeseries = + detailedStatistics?.previousPeriod?.[groupId]?.timeseries; + const { currentPeriodColor, previousPeriodColor } = + getTimeSeriesColor(ChartType.FAILED_TRANSACTION_RATE); + + return ( + + ); + }, + }, + ] as Array>; + }, [ + serviceName, + query, + detailedStatistics, + comparisonEnabled, + detailedStatisticsLoading, + offset, + ]); + return ( + + ); +} + +export { MobileCrashGroupList }; diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crashes_overview.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crashes_overview.tsx new file mode 100644 index 0000000000000..9ebd3a75e44bb --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/crashes_overview.tsx @@ -0,0 +1,267 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { orderBy } from 'lodash'; +import { v4 as uuidv4 } from 'uuid'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { useCrashGroupDistributionFetcher } from '../../../../hooks/use_crash_group_distribution_fetcher'; +import { MobileErrorsAndCrashesTreemap } from '../charts/mobile_errors_and_crashes_treemap'; +import { MobileCrashGroupList } from './crash_group_list'; +import { + FETCH_STATUS, + isPending, + useFetcher, +} from '../../../../hooks/use_fetcher'; +import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { ErrorDistribution } from '../errors_and_crashes_group_details/shared/distribution'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options'; +import { + getKueryWithMobileCrashFilter, + getKueryWithMobileFilters, +} from '../../../../../common/utils/get_kuery_with_mobile_filters'; + +type MobileCrashGroupMainStatistics = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>; +type MobileCrashGroupDetailedStatistics = + APIReturnType<'POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics'>; + +const INITIAL_STATE_MAIN_STATISTICS: { + mobileCrashGroupMainStatistics: MobileCrashGroupMainStatistics['errorGroups']; + requestId?: string; + currentPageGroupIds: MobileCrashGroupMainStatistics['errorGroups']; +} = { + mobileCrashGroupMainStatistics: [], + requestId: undefined, + currentPageGroupIds: [], +}; + +const INITIAL_STATE_DETAILED_STATISTICS: MobileCrashGroupDetailedStatistics = { + currentPeriod: {}, + previousPeriod: {}, +}; + +export function MobileCrashesOverview() { + const { serviceName } = useApmServiceContext(); + + const { + query: { + environment, + kuery, + sortField = 'occurrences', + sortDirection = 'desc', + rangeFrom, + rangeTo, + offset, + comparisonEnabled, + page = 0, + pageSize = 25, + device, + osVersion, + appVersion, + netConnectionType, + }, + } = useApmParams('/mobile-services/{serviceName}/errors-and-crashes/'); + + const kueryWithMobileFilters = getKueryWithMobileFilters({ + device, + osVersion, + appVersion, + netConnectionType, + kuery, + }); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { crashDistributionData, status } = useCrashGroupDistributionFetcher({ + serviceName, + groupId: undefined, + environment, + kuery: kueryWithMobileFilters, + }); + + const { + data: crashGroupListData = INITIAL_STATE_MAIN_STATISTICS, + status: crashGroupListDataStatus, + } = useFetcher( + (callApmApi) => { + const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; + + if (start && end) { + return callApmApi( + 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics', + { + params: { + path: { + serviceName, + }, + query: { + environment, + kuery: kueryWithMobileFilters, + start, + end, + sortField, + sortDirection: normalizedSortDirection, + }, + }, + } + ).then((response) => { + const currentPageGroupIds = orderBy( + response.errorGroups, + sortField, + sortDirection + ) + .slice(page * pageSize, (page + 1) * pageSize) + .map(({ groupId }) => groupId) + .sort(); + + return { + // Everytime the main statistics is refetched, updates the requestId making the comparison API to be refetched. + requestId: uuidv4(), + mobileCrashGroupMainStatistics: response.errorGroups, + currentPageGroupIds, + }; + }); + } + }, + [ + environment, + kueryWithMobileFilters, + serviceName, + start, + end, + sortField, + sortDirection, + page, + pageSize, + ] + ); + + const { requestId, mobileCrashGroupMainStatistics, currentPageGroupIds } = + crashGroupListData; + const { + data: mobileCrashGroupDetailedStatistics = INITIAL_STATE_DETAILED_STATISTICS, + status: mobileCrashGroupDetailedStatisticsStatus, + } = useFetcher( + (callApmApi) => { + if (requestId && currentPageGroupIds.length && start && end) { + return callApmApi( + 'POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics', + { + params: { + path: { serviceName }, + query: { + environment, + kuery: kueryWithMobileFilters, + start, + end, + numBuckets: 20, + offset: + comparisonEnabled && isTimeComparison(offset) + ? offset + : undefined, + }, + body: { + groupIds: JSON.stringify(currentPageGroupIds), + }, + }, + } + ); + } + }, + // only fetches agg results when requestId changes + // eslint-disable-next-line react-hooks/exhaustive-deps + [requestId], + { preservePreviousData: false } + ); + + const kueryForTreemap = getKueryWithMobileCrashFilter({ + kuery: kueryWithMobileFilters, + groupId: undefined, + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.apm.serviceDetails.metrics.crashes.title', + { defaultMessage: 'Crashes' } + )} +

+
+ + + +
+
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/error_group_list.stories.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/error_group_list.stories.tsx new file mode 100644 index 0000000000000..9e564a930a9a7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/error_group_list.stories.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta, Story } from '@storybook/react'; +import React, { ComponentProps } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../../context/url_params_context/mock_url_params_context_provider'; + +import { MobileErrorGroupList } from '.'; + +type Args = ComponentProps; + +const stories: Meta = { + title: 'app/ErrorGroupOverview/MobileErrorGroupList', + component: MobileErrorGroupList, + decorators: [ + (StoryComponent) => { + return ( + + + + + + + + ); + }, + ], +}; +export default stories; + +export const Example: Story = (args) => { + return ; +}; +Example.args = { + mainStatistics: [ + { + name: 'net/http: abort Handler', + occurrences: 14, + culprit: 'Main.func2', + groupId: '83a653297ec29afed264d7b60d5cda7b', + lastSeen: 1634833121434, + handled: false, + type: 'errorString', + }, + { + name: 'POST /api/orders (500)', + occurrences: 5, + culprit: 'logrusMiddleware', + groupId: '7a640436a9be648fd708703d1ac84650', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + { + name: 'write tcp 10.36.2.24:3000->10.36.1.14:34232: write: connection reset by peer', + occurrences: 4, + culprit: 'apiHandlers.getProductCustomers', + groupId: '95ca0e312c109aa11e298bcf07f1445b', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + { + name: 'write tcp 10.36.0.21:3000->10.36.1.252:57070: write: connection reset by peer', + occurrences: 3, + culprit: 'apiHandlers.getCustomers', + groupId: '4053d7e33d2b716c819bd96d9d6121a2', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + { + name: 'write tcp 10.36.0.21:3000->10.36.0.88:33926: write: broken pipe', + occurrences: 2, + culprit: 'apiHandlers.getOrders', + groupId: '94f4ca8ec8c02e5318cf03f46ae4c1f3', + lastSeen: 1634833121434, + handled: false, + type: 'OpError', + }, + ], + serviceName: 'test service', +}; + +export const EmptyState: Story = (args) => { + return ; +}; +EmptyState.args = { + mainStatistics: [], + serviceName: 'test service', +}; diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/error_group_list.test.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/error_group_list.test.tsx new file mode 100644 index 0000000000000..278825c25c68c --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/error_group_list.test.tsx @@ -0,0 +1,19 @@ +/* + * 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 { composeStories } from '@storybook/testing-react'; +import { render } from '@testing-library/react'; +import React from 'react'; +import * as stories from './error_group_list.stories'; + +const { Example } = composeStories(stories); + +describe('ErrorGroupList', () => { + it('renders', () => { + expect(() => render()).not.toThrowError(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/index.tsx new file mode 100644 index 0000000000000..e179410a5a20e --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/error_group_list/index.tsx @@ -0,0 +1,223 @@ +/* + * 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 { + EuiBadge, + EuiToolTip, + RIGHT_ALIGNMENT, + LEFT_ALIGNMENT, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import React, { useMemo } from 'react'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; +import { asInteger } from '../../../../../../common/utils/formatters'; +import { useApmParams } from '../../../../../hooks/use_apm_params'; +import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; +import { truncate } from '../../../../../utils/style'; +import { + ChartType, + getTimeSeriesColor, +} from '../../../../shared/charts/helper/get_timeseries_color'; +import { SparkPlot } from '../../../../shared/charts/spark_plot'; +import { ErrorDetailLink } from '../../../../shared/links/apm/mobile/error_detail_link'; +import { ErrorOverviewLink } from '../../../../shared/links/apm/mobile/error_overview_link'; +import { ITableColumn, ManagedTable } from '../../../../shared/managed_table'; +import { TimestampTooltip } from '../../../../shared/timestamp_tooltip'; +import { isTimeComparison } from '../../../../shared/time_comparison/get_comparison_options'; + +const MessageAndCulpritCell = euiStyled.div` + ${truncate('100%')}; +`; + +const ErrorLink = euiStyled(ErrorOverviewLink)` + ${truncate('100%')}; +`; + +type ErrorGroupItem = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics'>['errorGroups'][0]; +type ErrorGroupDetailedStatistics = + APIReturnType<'POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics'>; + +interface Props { + mainStatistics: ErrorGroupItem[]; + serviceName: string; + detailedStatisticsLoading: boolean; + detailedStatistics: ErrorGroupDetailedStatistics; + initialSortField: string; + initialSortDirection: 'asc' | 'desc'; + comparisonEnabled?: boolean; + isLoading: boolean; +} + +function MobileErrorGroupList({ + mainStatistics, + serviceName, + detailedStatisticsLoading, + detailedStatistics, + comparisonEnabled, + initialSortField, + initialSortDirection, + isLoading, +}: Props) { + const { query } = useApmParams( + '/mobile-services/{serviceName}/errors-and-crashes' + ); + const { offset } = query; + const columns = useMemo(() => { + return [ + { + name: i18n.translate('xpack.apm.errorsTable.typeColumnLabel', { + defaultMessage: 'Type', + }), + field: 'type', + sortable: false, + render: (_, { type }) => { + return ( + + {type} + + ); + }, + }, + { + name: i18n.translate( + 'xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel', + { + defaultMessage: 'Error message and culprit', + } + ), + field: 'message', + sortable: false, + width: '30%', + render: (_, item: ErrorGroupItem) => { + return ( + + + + {item.name || NOT_AVAILABLE_LABEL} + + + + ); + }, + }, + { + name: '', + field: 'handled', + sortable: false, + align: RIGHT_ALIGNMENT, + render: (_, { handled }) => + handled === false && ( + + {i18n.translate('xpack.apm.errorsTable.unhandledLabel', { + defaultMessage: 'Unhandled', + })} + + ), + }, + { + field: 'lastSeen', + sortable: true, + name: i18n.translate('xpack.apm.errorsTable.lastSeenColumnLabel', { + defaultMessage: 'Last seen', + }), + align: LEFT_ALIGNMENT, + render: (_, { lastSeen }) => + lastSeen ? ( + + ) : ( + NOT_AVAILABLE_LABEL + ), + }, + { + field: 'occurrences', + name: i18n.translate('xpack.apm.errorsTable.occurrencesColumnLabel', { + defaultMessage: 'Occurrences', + }), + sortable: true, + dataType: 'number', + align: RIGHT_ALIGNMENT, + render: (_, { occurrences, groupId }) => { + const currentPeriodTimeseries = + detailedStatistics?.currentPeriod?.[groupId]?.timeseries; + const previousPeriodTimeseries = + detailedStatistics?.previousPeriod?.[groupId]?.timeseries; + const { currentPeriodColor, previousPeriodColor } = + getTimeSeriesColor(ChartType.FAILED_TRANSACTION_RATE); + + return ( + + ); + }, + }, + ] as Array>; + }, [ + serviceName, + query, + detailedStatistics, + comparisonEnabled, + detailedStatisticsLoading, + offset, + ]); + return ( + + ); +} + +export { MobileErrorGroupList }; diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/errors_overview.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/errors_overview.tsx new file mode 100644 index 0000000000000..56d3ee35fc32c --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/errors_overview.tsx @@ -0,0 +1,275 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { orderBy } from 'lodash'; +import React from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useErrorGroupDistributionFetcher } from '../../../../hooks/use_error_group_distribution_fetcher'; +import { + FETCH_STATUS, + isPending, + useFetcher, +} from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options'; +import { HttpErrorRateChart } from '../charts/mobile_http_error_rate'; +import { ErrorDistribution } from '../errors_and_crashes_group_details/shared/distribution'; +import { MobileErrorGroupList } from './error_group_list'; +import { MobileErrorsAndCrashesTreemap } from '../charts/mobile_errors_and_crashes_treemap'; +import { + getKueryWithMobileErrorFilter, + getKueryWithMobileFilters, +} from '../../../../../common/utils/get_kuery_with_mobile_filters'; + +type MobileErrorGroupMainStatistics = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics'>; +type MobileErrorGroupDetailedStatistics = + APIReturnType<'POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics'>; + +const INITIAL_STATE_MAIN_STATISTICS: { + mobileErrorGroupMainStatistics: MobileErrorGroupMainStatistics['errorGroups']; + requestId?: string; + currentPageGroupIds: MobileErrorGroupMainStatistics['errorGroups']; +} = { + mobileErrorGroupMainStatistics: [], + requestId: undefined, + currentPageGroupIds: [], +}; + +const INITIAL_STATE_DETAILED_STATISTICS: MobileErrorGroupDetailedStatistics = { + currentPeriod: {}, + previousPeriod: {}, +}; + +export function MobileErrorsOverview() { + const { serviceName } = useApmServiceContext(); + const { + query: { + environment, + kuery, + sortField = 'occurrences', + sortDirection = 'desc', + rangeFrom, + rangeTo, + offset, + comparisonEnabled, + page = 0, + pageSize = 25, + device, + osVersion, + appVersion, + netConnectionType, + }, + } = useApmParams('/mobile-services/{serviceName}/errors-and-crashes'); + const kueryWithMobileFilters = getKueryWithMobileFilters({ + device, + osVersion, + appVersion, + netConnectionType, + kuery, + }); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { errorDistributionData, status } = useErrorGroupDistributionFetcher({ + serviceName, + groupId: undefined, + environment, + kuery: kueryWithMobileFilters, + }); + const { + data: errorGroupListData = INITIAL_STATE_MAIN_STATISTICS, + status: errorGroupListDataStatus, + } = useFetcher( + (callApmApi) => { + const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; + + if (start && end) { + return callApmApi( + 'GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics', + { + params: { + path: { + serviceName, + }, + query: { + environment, + kuery: kueryWithMobileFilters, + start, + end, + sortField, + sortDirection: normalizedSortDirection, + }, + }, + } + ).then((response) => { + const currentPageGroupIds = orderBy( + response.errorGroups, + sortField, + sortDirection + ) + .slice(page * pageSize, (page + 1) * pageSize) + .map(({ groupId }) => groupId) + .sort(); + + return { + // Everytime the main statistics is refetched, updates the requestId making the comparison API to be refetched. + requestId: uuidv4(), + mobileErrorGroupMainStatistics: response.errorGroups, + currentPageGroupIds, + }; + }); + } + }, + [ + environment, + kueryWithMobileFilters, + serviceName, + start, + end, + sortField, + sortDirection, + page, + pageSize, + ] + ); + const { requestId, mobileErrorGroupMainStatistics, currentPageGroupIds } = + errorGroupListData; + const { + data: mobileErrorGroupDetailedStatistics = INITIAL_STATE_DETAILED_STATISTICS, + status: mobileErrorGroupDetailedStatisticsStatus, + } = useFetcher( + (callApmApi) => { + if (requestId && currentPageGroupIds.length && start && end) { + return callApmApi( + 'POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics', + { + params: { + path: { serviceName }, + query: { + environment, + kuery: kueryWithMobileFilters, + start, + end, + numBuckets: 20, + offset: + comparisonEnabled && isTimeComparison(offset) + ? offset + : undefined, + }, + body: { + groupIds: JSON.stringify(currentPageGroupIds), + }, + }, + } + ); + } + }, + // only fetches agg results when requestId changes + // eslint-disable-next-line react-hooks/exhaustive-deps + [requestId], + { preservePreviousData: false } + ); + const kueryForTreemap = getKueryWithMobileErrorFilter({ + kuery: kueryWithMobileFilters, + groupId: undefined, + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.apm.serviceDetails.metrics.errorsList.title', + { defaultMessage: 'Errors' } + )} +

+
+ + + +
+
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/index.tsx new file mode 100644 index 0000000000000..539a0efc01709 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/index.tsx @@ -0,0 +1,36 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { Tabs, MobileErrorTabIds } from './tabs/tabs'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { push } from '../../../shared/links/url_helpers'; + +export function MobileErrorCrashesOverview() { + const { + query: { mobileErrorTabId = MobileErrorTabIds.ERRORS }, + } = useApmParams('/mobile-services/{serviceName}/errors-and-crashes'); + const history = useHistory(); + return ( + + + + { + push(history, { + query: { + mobileErrorTabId: nextTab, + }, + }); + }} + mobileErrorTabId={mobileErrorTabId as MobileErrorTabIds} + /> + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/tabs/tabs.tsx b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/tabs/tabs.tsx new file mode 100644 index 0000000000000..8cdcd231d9338 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/mobile/errors_and_crashes_overview/tabs/tabs.tsx @@ -0,0 +1,67 @@ +/* + * 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 { EuiTab, EuiTabs, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { MobileErrorsOverview } from '../errors_overview'; +import { MobileCrashesOverview } from '../crashes_overview'; + +export enum MobileErrorTabIds { + ERRORS = 'errors', + CRASHES = 'crashes', +} + +const tabs = [ + { + id: MobileErrorTabIds.ERRORS, + name: i18n.translate('xpack.apm.mobile.errorsAndCrashes.errorsTab', { + defaultMessage: 'Errors', + }), + 'data-test-subj': 'apmMobileErrorsTabButton', + }, + { + id: MobileErrorTabIds.CRASHES, + name: i18n.translate('xpack.apm.mobile.errorsAndCrashes.crashesTab', { + defaultMessage: 'Crashes', + }), + append:
, + 'data-test-subj': 'apmMobileCrashesTabButton', + }, +]; + +export function Tabs({ + mobileErrorTabId, + onTabClick, +}: { + mobileErrorTabId: MobileErrorTabIds; + onTabClick: (nextTab: MobileErrorTabIds) => void; +}) { + const selectedTabId = mobileErrorTabId; + const tabEntries = tabs.map((tab, index) => ( + { + onTabClick(tab.id); + }} + isSelected={tab.id === selectedTabId} + append={tab.append} + > + {tab.name} + + )); + + return ( + <> + {tabEntries} + + {selectedTabId === MobileErrorTabIds.ERRORS && } + {selectedTabId === MobileErrorTabIds.CRASHES && } + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx index 01ac1451e23e6..b92af98de2d64 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/filters/index.tsx @@ -77,7 +77,8 @@ export function MobileFilters() { } = useAnyOfApmParams( '/mobile-services/{serviceName}/overview', '/mobile-services/{serviceName}/transactions', - '/mobile-services/{serviceName}/transactions/view' + '/mobile-services/{serviceName}/transactions/view', + '/mobile-services/{serviceName}/errors-and-crashes' ); const filters = { netConnectionType, device, osVersion, appVersion }; diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx index df322ea7fe372..b4c68a14b07c3 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx @@ -15,9 +15,7 @@ import { EuiPanel, EuiSpacer, EuiTitle, - EuiCallOut, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; @@ -111,42 +109,6 @@ export function MobileServiceOverview() { - - -

- - {i18n.translate( - 'xpack.apm.serviceOverview.mobileCallOutLink', - { - defaultMessage: 'Give feedback', - } - )} - - ), - }} - /> -

-
- -
, + searchBarOptions: { + showTimeComparison: true, + showMobileFilters: true, + }, + }), + params: t.partial({ + query: t.partial({ + page: toNumberRt, + pageSize: toNumberRt, + sortField: t.string, + sortDirection: t.union([t.literal('asc'), t.literal('desc')]), + mobileErrorTabId: t.string, + device: t.string, + osVersion: t.string, + appVersion: t.string, + netConnectionType: t.string, + }), + }), + children: { + '/mobile-services/{serviceName}/errors-and-crashes/errors/{groupId}': + { + element: , + params: t.type({ + path: t.type({ + groupId: t.string, + }), + query: t.partial({ errorId: t.string }), + }), + }, + '/mobile-services/{serviceName}/errors-and-crashes/': { + element: , + }, + '/mobile-services/{serviceName}/errors-and-crashes/crashes/{groupId}': + { + element: , + params: t.type({ + path: t.type({ + groupId: t.string, + }), + query: t.partial({ errorId: t.string }), + }), + }, + }, + }, + '/mobile-services/{serviceName}/dependencies': page({ + element: , + tabKey: 'dependencies', + title: i18n.translate('xpack.apm.views.dependencies.title', { + defaultMessage: 'Dependencies', + }), + searchBarOptions: { + showTimeComparison: true, + }, + }), '/mobile-services/{serviceName}/service-map': page({ tabKey: 'service-map', title: i18n.translate('xpack.apm.views.serviceMap.title', { diff --git a/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx index acddb4677bf07..3c9bde66ff3ac 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx @@ -24,13 +24,18 @@ import { useTimeRange } from '../../../../hooks/use_time_range'; import { getAlertingCapabilities } from '../../../alerting/utils/get_alerting_capabilities'; import { MobileSearchBar } from '../../../app/mobile/search_bar'; import { ServiceIcons } from '../../../shared/service_icons'; -import { BetaBadge } from '../../../shared/beta_badge'; import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge'; import { ApmMainTemplate } from '../apm_main_template'; import { AnalyzeDataButton } from '../apm_service_template/analyze_data_button'; type Tab = NonNullable[0] & { - key: 'overview' | 'transactions' | 'service-map' | 'alerts'; + key: + | 'overview' + | 'transactions' + | 'dependencies' + | 'errors-and-crashes' + | 'service-map' + | 'alerts'; hidden?: boolean; }; @@ -122,9 +127,6 @@ function TemplateWithContext({ end={end} /> - - - @@ -190,6 +192,26 @@ function useTabs({ selectedTabKey }: { selectedTabKey: Tab['key'] }) { } ), }, + { + key: 'dependencies', + href: router.link('/mobile-services/{serviceName}/dependencies', { + path: { serviceName }, + query, + }), + label: i18n.translate('xpack.apm.serviceDetails.dependenciesTabLabel', { + defaultMessage: 'Dependencies', + }), + }, + { + key: 'errors-and-crashes', + href: router.link('/mobile-services/{serviceName}/errors-and-crashes', { + path: { serviceName }, + query, + }), + label: i18n.translate('xpack.apm.serviceDetails.mobileErrorsTabLabel', { + defaultMessage: 'Errors & Crashes', + }), + }, { key: 'service-map', href: router.link('/mobile-services/{serviceName}/service-map', { diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/mobile/crash_detail_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/mobile/crash_detail_link.tsx new file mode 100644 index 0000000000000..73d79529cf70f --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/links/apm/mobile/crash_detail_link.tsx @@ -0,0 +1,47 @@ +/* + * 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 { TypeOf } from '@kbn/typed-react-router-config'; +import { EuiLink } from '@elastic/eui'; +import { mobileServiceDetailRoute } from '../../../../routing/mobile_service_detail'; +import { useApmRouter } from '../../../../../hooks/use_apm_router'; + +interface Props { + children: React.ReactNode; + title?: string; + serviceName: string; + groupId: string; + query: TypeOf< + typeof mobileServiceDetailRoute, + '/mobile-services/{serviceName}/errors-and-crashes' + >['query']; +} + +function CrashDetailLink({ serviceName, groupId, query, ...rest }: Props) { + const router = useApmRouter(); + const crashDetailsLink = router.link( + `/mobile-services/{serviceName}/errors-and-crashes/crashes/{groupId}`, + { + path: { + serviceName, + groupId, + }, + query, + } + ); + + return ( + + ); +} + +export { CrashDetailLink }; diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/mobile/error_detail_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/mobile/error_detail_link.tsx new file mode 100644 index 0000000000000..7c77846fdaee8 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/links/apm/mobile/error_detail_link.tsx @@ -0,0 +1,47 @@ +/* + * 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 { TypeOf } from '@kbn/typed-react-router-config'; +import { EuiLink } from '@elastic/eui'; +import { useApmRouter } from '../../../../../hooks/use_apm_router'; +import { mobileServiceDetailRoute } from '../../../../routing/mobile_service_detail'; + +interface Props { + children: React.ReactNode; + title?: string; + serviceName: string; + groupId: string; + query: TypeOf< + typeof mobileServiceDetailRoute, + '/mobile-services/{serviceName}/errors-and-crashes' + >['query']; +} + +function ErrorDetailLink({ serviceName, groupId, query, ...rest }: Props) { + const router = useApmRouter(); + const errorDetailsLink = router.link( + `/mobile-services/{serviceName}/errors-and-crashes/errors/{groupId}`, + { + path: { + serviceName, + groupId, + }, + query, + } + ); + + return ( + + ); +} + +export { ErrorDetailLink }; diff --git a/x-pack/plugins/apm/public/components/shared/links/apm/mobile/error_overview_link.tsx b/x-pack/plugins/apm/public/components/shared/links/apm/mobile/error_overview_link.tsx new file mode 100644 index 0000000000000..ad4f337079521 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/links/apm/mobile/error_overview_link.tsx @@ -0,0 +1,43 @@ +/* + * 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 { EuiLink } from '@elastic/eui'; +import { TypeOf } from '@kbn/typed-react-router-config'; +import { useApmRouter } from '../../../../../hooks/use_apm_router'; +import { mobileServiceDetailRoute } from '../../../../routing/mobile_service_detail'; + +interface Props { + children: React.ReactNode; + title?: string; + serviceName: string; + query: TypeOf< + typeof mobileServiceDetailRoute, + '/mobile-services/{serviceName}/errors-and-crashes' + >['query']; +} + +export function ErrorOverviewLink({ serviceName, query, ...rest }: Props) { + const router = useApmRouter(); + const errorOverviewLink = router.link( + '/mobile-services/{serviceName}/errors-and-crashes', + { + path: { + serviceName, + }, + query, + } + ); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/hooks/use_crash_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_crash_group_distribution_fetcher.tsx new file mode 100644 index 0000000000000..e094b2440f0b1 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_crash_group_distribution_fetcher.tsx @@ -0,0 +1,69 @@ +/* + * 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 { isTimeComparison } from '../components/shared/time_comparison/get_comparison_options'; +import { useAnyOfApmParams } from './use_apm_params'; +import { useFetcher } from './use_fetcher'; +import { useTimeRange } from './use_time_range'; + +export function useCrashGroupDistributionFetcher({ + serviceName, + groupId, + kuery, + environment, +}: { + serviceName: string; + groupId: string | undefined; + kuery: string; + environment: string; +}) { + const { + query: { rangeFrom, rangeTo, offset, comparisonEnabled }, + } = useAnyOfApmParams( + '/services/{serviceName}/errors', + '/mobile-services/{serviceName}/errors-and-crashes' + ); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const { data, status } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi( + 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution', + { + params: { + path: { serviceName }, + query: { + environment, + kuery, + start, + end, + offset: + comparisonEnabled && isTimeComparison(offset) + ? offset + : undefined, + groupId, + }, + }, + } + ); + } + }, + [ + environment, + kuery, + serviceName, + start, + end, + offset, + groupId, + comparisonEnabled, + ] + ); + + return { crashDistributionData: data, status }; +} diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx index 2aa64472aa1d1..bc8a2056cb41c 100644 --- a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { isTimeComparison } from '../components/shared/time_comparison/get_comparison_options'; -import { useApmParams } from './use_apm_params'; +import { useAnyOfApmParams } from './use_apm_params'; import { useFetcher } from './use_fetcher'; import { useTimeRange } from './use_time_range'; @@ -22,7 +22,10 @@ export function useErrorGroupDistributionFetcher({ }) { const { query: { rangeFrom, rangeTo, offset, comparisonEnabled }, - } = useApmParams('/services/{serviceName}/errors'); + } = useAnyOfApmParams( + '/services/{serviceName}/errors', + '/mobile-services/{serviceName}/errors-and-crashes' + ); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); diff --git a/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts index f4e6c1aaa98df..dbe3069995711 100644 --- a/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts @@ -165,7 +165,6 @@ export function registerRoutes({ _inspect: inspectableEsQueriesMap.get(request), } : { ...data }; - if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/get_buckets.test.ts.snap b/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/get_buckets.test.ts.snap index 4145b9ae31da1..7d8d396a01de0 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/get_buckets.test.ts.snap +++ b/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/get_buckets.test.ts.snap @@ -50,6 +50,11 @@ Array [ }, }, ], + "must_not": Object { + "term": Object { + "error.type": "crash", + }, + }, }, }, "size": 0, diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/queries.test.ts.snap index 480283b7a690c..3ef42dc70bcee 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/routes/errors/distribution/__snapshots__/queries.test.ts.snap @@ -42,6 +42,11 @@ Object { }, }, ], + "must_not": Object { + "term": Object { + "error.type": "crash", + }, + }, }, }, "size": 0, @@ -97,6 +102,11 @@ Object { }, }, ], + "must_not": Object { + "term": Object { + "error.type": "crash", + }, + }, }, }, "size": 0, diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts index 83e9d4475bfb8..b0e6835ba921a 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts @@ -49,6 +49,9 @@ export async function getBuckets({ size: 0, query: { bool: { + must_not: { + term: { 'error.type': 'crash' }, + }, filter: [ { term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end), diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/__snapshots__/get_buckets.test.ts.snap b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/__snapshots__/get_buckets.test.ts.snap new file mode 100644 index 0000000000000..5c8b2ed593e99 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/__snapshots__/get_buckets.test.ts.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`get buckets should make the correct query 1`] = ` +Array [ + Array [ + "get_error_distribution_buckets", + Object { + "apm": Object { + "events": Array [ + "error", + ], + }, + "body": Object { + "aggs": Object { + "distribution": Object { + "histogram": Object { + "extended_bounds": Object { + "max": 1528977600000, + "min": 1528113600000, + }, + "field": "@timestamp", + "interval": 10, + "min_doc_count": 0, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "error.type": "crash", + }, + }, + Object { + "term": Object { + "service.name": "myServiceName", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "service.environment": "prod", + }, + }, + ], + }, + }, + "size": 0, + "track_total_hits": false, + }, + }, + ], +] +`; diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/__snapshots__/queries.test.ts.snap new file mode 100644 index 0000000000000..447ac8b736338 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/__snapshots__/queries.test.ts.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`error distribution queries fetches an error distribution 1`] = ` +Object { + "apm": Object { + "events": Array [ + "error", + ], + }, + "body": Object { + "aggs": Object { + "distribution": Object { + "histogram": Object { + "extended_bounds": Object { + "max": 50000, + "min": 0, + }, + "field": "@timestamp", + "interval": 3333, + "min_doc_count": 0, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "error.type": "crash", + }, + }, + Object { + "term": Object { + "service.name": "serviceName", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 0, + "lte": 50000, + }, + }, + }, + ], + }, + }, + "size": 0, + "track_total_hits": false, + }, +} +`; + +exports[`error distribution queries fetches an error distribution with a group id 1`] = ` +Object { + "apm": Object { + "events": Array [ + "error", + ], + }, + "body": Object { + "aggs": Object { + "distribution": Object { + "histogram": Object { + "extended_bounds": Object { + "max": 50000, + "min": 0, + }, + "field": "@timestamp", + "interval": 3333, + "min_doc_count": 0, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "error.type": "crash", + }, + }, + Object { + "term": Object { + "service.name": "serviceName", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 0, + "lte": 50000, + }, + }, + }, + Object { + "term": Object { + "error.grouping_key": "foo", + }, + }, + ], + }, + }, + "size": 0, + "track_total_hits": false, + }, +} +`; diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_buckets.test.ts new file mode 100644 index 0000000000000..4a3fc6d969cd0 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_buckets.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { getBuckets } from './get_buckets'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; + +describe('get buckets', () => { + let clientSpy: jest.Mock; + + beforeEach(async () => { + clientSpy = jest.fn().mockResolvedValueOnce({ + hits: { + total: 100, + }, + aggregations: { + distribution: { + buckets: [], + }, + }, + }); + + await getBuckets({ + environment: 'prod', + serviceName: 'myServiceName', + bucketSize: 10, + kuery: '', + apmEventClient: { + search: clientSpy, + } as any, + start: 1528113600000, + end: 1528977600000, + }); + }); + + it('should make the correct query', () => { + expect(clientSpy.mock.calls).toMatchSnapshot(); + }); + + it('should limit query results to error documents', () => { + const query = clientSpy.mock.calls[0][1]; + expect(query.apm.events).toEqual([ProcessorEvent.error]); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_buckets.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_buckets.ts new file mode 100644 index 0000000000000..8fe2ec16c2d45 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_buckets.ts @@ -0,0 +1,88 @@ +/* + * 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 { + rangeQuery, + kqlQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + ERROR_GROUP_ID, + SERVICE_NAME, + ERROR_TYPE, +} from '../../../../../common/es_fields/apm'; +import { environmentQuery } from '../../../../../common/utils/environment_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; + +export async function getBuckets({ + environment, + kuery, + serviceName, + groupId, + bucketSize, + apmEventClient, + start, + end, +}: { + environment: string; + kuery: string; + serviceName: string; + groupId?: string; + bucketSize: number; + apmEventClient: APMEventClient; + start: number; + end: number; +}) { + const params = { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(ERROR_TYPE, 'crash'), + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(ERROR_GROUP_ID, groupId), + ], + }, + }, + aggs: { + distribution: { + histogram: { + field: '@timestamp', + min_doc_count: 0, + interval: bucketSize, + extended_bounds: { + min: start, + max: end, + }, + }, + }, + }, + }, + }; + + const resp = await apmEventClient.search( + 'get_error_distribution_buckets', + params + ); + + const buckets = (resp.aggregations?.distribution.buckets || []).map( + (bucket) => ({ + x: bucket.key, + y: bucket.doc_count, + }) + ); + return { buckets }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_distribution.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_distribution.ts new file mode 100644 index 0000000000000..1599ea3c8e87c --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/get_distribution.ts @@ -0,0 +1,93 @@ +/* + * 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 { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate'; +import { BUCKET_TARGET_COUNT } from '../../../transactions/constants'; +import { getBuckets } from './get_buckets'; +import { getOffsetInMs } from '../../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; +import { Maybe } from '../../../../../typings/common'; + +function getBucketSize({ start, end }: { start: number; end: number }) { + return Math.floor((end - start) / BUCKET_TARGET_COUNT); +} + +export interface CrashDistributionResponse { + currentPeriod: Array<{ x: number; y: number }>; + previousPeriod: Array<{ + x: number; + y: Maybe; + }>; + bucketSize: number; +} + +export async function getCrashDistribution({ + environment, + kuery, + serviceName, + groupId, + apmEventClient, + start, + end, + offset, +}: { + environment: string; + kuery: string; + serviceName: string; + groupId?: string; + apmEventClient: APMEventClient; + start: number; + end: number; + offset?: string; +}): Promise { + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const bucketSize = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + }); + + const commonProps = { + environment, + kuery, + serviceName, + groupId, + apmEventClient, + bucketSize, + }; + const currentPeriodPromise = getBuckets({ + ...commonProps, + start, + end, + }); + + const previousPeriodPromise = offset + ? getBuckets({ + ...commonProps, + start: startWithOffset, + end: endWithOffset, + }) + : { buckets: [], bucketSize: null }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + + return { + currentPeriod: currentPeriod.buckets, + previousPeriod: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.buckets, + previousPeriodTimeseries: previousPeriod.buckets, + }), + bucketSize, + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/queries.test.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/queries.test.ts new file mode 100644 index 0000000000000..70232a66abb48 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/distribution/queries.test.ts @@ -0,0 +1,52 @@ +/* + * 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 { getCrashDistribution } from './get_distribution'; +import { + SearchParamsMock, + inspectSearchParams, +} from '../../../../utils/test_helpers'; +import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; + +describe('error distribution queries', () => { + let mock: SearchParamsMock; + + afterEach(() => { + mock.teardown(); + }); + + it('fetches an error distribution', async () => { + mock = await inspectSearchParams(({ mockApmEventClient }) => + getCrashDistribution({ + serviceName: 'serviceName', + apmEventClient: mockApmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 50000, + }) + ); + + expect(mock.params).toMatchSnapshot(); + }); + + it('fetches an error distribution with a group id', async () => { + mock = await inspectSearchParams(({ mockApmEventClient }) => + getCrashDistribution({ + serviceName: 'serviceName', + groupId: 'foo', + apmEventClient: mockApmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 50000, + }) + ); + + expect(mock.params).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts new file mode 100644 index 0000000000000..2bb38c63b3b51 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/get_crash_groups/get_crash_group_main_statistics.ts @@ -0,0 +1,145 @@ +/* + * 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 { AggregationsAggregateOrder } from '@elastic/elasticsearch/lib/api/types'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + ERROR_CULPRIT, + ERROR_TYPE, + ERROR_EXC_HANDLED, + ERROR_EXC_MESSAGE, + ERROR_EXC_TYPE, + ERROR_GROUP_ID, + ERROR_LOG_MESSAGE, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../../common/es_fields/apm'; +import { environmentQuery } from '../../../../../common/utils/environment_query'; +import { getErrorName } from '../../../../lib/helpers/get_error_name'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; + +export type MobileCrashGroupMainStatisticsResponse = Array<{ + groupId: string; + name: string; + lastSeen: number; + occurrences: number; + culprit: string | undefined; + handled: boolean | undefined; + type: string | undefined; +}>; + +export async function getMobileCrashGroupMainStatistics({ + kuery, + serviceName, + apmEventClient, + environment, + sortField, + sortDirection = 'desc', + start, + end, + maxNumberOfErrorGroups = 500, + transactionName, + transactionType, +}: { + kuery: string; + serviceName: string; + apmEventClient: APMEventClient; + environment: string; + sortField?: string; + sortDirection?: 'asc' | 'desc'; + start: number; + end: number; + maxNumberOfErrorGroups?: number; + transactionName?: string; + transactionType?: string; +}): Promise { + // sort buckets by last occurrence of error + const sortByLatestOccurrence = sortField === 'lastSeen'; + + const maxTimestampAggKey = 'max_timestamp'; + + const order: AggregationsAggregateOrder = sortByLatestOccurrence + ? { [maxTimestampAggKey]: sortDirection } + : { _count: sortDirection }; + + const response = await apmEventClient.search( + 'get_crash_group_main_statistics', + { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_NAME, transactionName), + ...termQuery(TRANSACTION_TYPE, transactionType), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...termQuery(ERROR_TYPE, 'crash'), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + crash_groups: { + terms: { + field: ERROR_GROUP_ID, + size: maxNumberOfErrorGroups, + order, + }, + aggs: { + sample: { + top_hits: { + size: 1, + _source: [ + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ERROR_CULPRIT, + ERROR_GROUP_ID, + '@timestamp', + ], + sort: { + '@timestamp': 'desc', + }, + }, + }, + ...(sortByLatestOccurrence + ? { [maxTimestampAggKey]: { max: { field: '@timestamp' } } } + : {}), + }, + }, + }, + }, + } + ); + + return ( + response.aggregations?.crash_groups.buckets.map((bucket) => ({ + groupId: bucket.key as string, + name: getErrorName(bucket.sample.hits.hits[0]._source), + lastSeen: new Date( + bucket.sample.hits.hits[0]?._source['@timestamp'] + ).getTime(), + occurrences: bucket.doc_count, + culprit: bucket.sample.hits.hits[0]?._source.error.culprit, + handled: bucket.sample.hits.hits[0]?._source.error.exception?.[0].handled, + type: bucket.sample.hits.hits[0]?._source.error.exception?.[0].type, + })) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/get_mobile_crash_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/get_mobile_crash_group_detailed_statistics.ts new file mode 100644 index 0000000000000..71f6a9ef152eb --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/get_mobile_crash_group_detailed_statistics.ts @@ -0,0 +1,199 @@ +/* + * 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 { keyBy } from 'lodash'; +import { + rangeQuery, + kqlQuery, + termQuery, + termsQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; +import { Coordinate } from '../../../../typings/timeseries'; +import { + ERROR_GROUP_ID, + ERROR_TYPE, + SERVICE_NAME, +} from '../../../../common/es_fields/apm'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { getBucketSize } from '../../../../common/utils/get_bucket_size'; +import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +interface CrashGroupDetailedStat { + groupId: string; + timeseries: Coordinate[]; +} + +export async function getMobileCrashesGroupDetailedStatistics({ + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + environment, + start, + end, + offset, +}: { + kuery: string; + serviceName: string; + apmEventClient: APMEventClient; + numBuckets: number; + groupIds: string[]; + environment: string; + start: number; + end: number; + offset?: string; +}): Promise { + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const { intervalString } = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + numBuckets, + }); + + const timeseriesResponse = await apmEventClient.search( + 'get_service_error_group_detailed_statistics', + { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termsQuery(ERROR_GROUP_ID, ...groupIds), + ...termsQuery(ERROR_TYPE, 'crash'), + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size: 500, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: startWithOffset, + max: endWithOffset, + }, + }, + }, + }, + }, + }, + }, + } + ); + + if (!timeseriesResponse.aggregations) { + return []; + } + + return timeseriesResponse.aggregations.error_groups.buckets.map((bucket) => { + const groupId = bucket.key as string; + return { + groupId, + timeseries: bucket.timeseries.buckets.map((timeseriesBucket) => { + return { + x: timeseriesBucket.key, + y: timeseriesBucket.doc_count, + }; + }), + }; + }); +} + +export interface MobileCrashesGroupPeriodsResponse { + currentPeriod: Record; + previousPeriod: Record; +} + +export async function getMobileCrashesGroupPeriods({ + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + environment, + start, + end, + offset, +}: { + kuery: string; + serviceName: string; + apmEventClient: APMEventClient; + numBuckets: number; + groupIds: string[]; + environment: string; + start: number; + end: number; + offset?: string; +}): Promise { + const commonProps = { + environment, + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + }; + + const currentPeriodPromise = getMobileCrashesGroupDetailedStatistics({ + ...commonProps, + start, + end, + }); + + const previousPeriodPromise = offset + ? getMobileCrashesGroupDetailedStatistics({ + ...commonProps, + start, + end, + offset, + }) + : []; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + + const firstCurrentPeriod = currentPeriod?.[0]; + + return { + currentPeriod: keyBy(currentPeriod, 'groupId'), + previousPeriod: keyBy( + previousPeriod.map((crashRateGroup) => ({ + ...crashRateGroup, + timeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firstCurrentPeriod?.timeseries, + previousPeriodTimeseries: crashRateGroup.timeseries, + }), + })), + 'groupId' + ), + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/crashes/route.ts b/x-pack/plugins/apm/server/routes/mobile/crashes/route.ts new file mode 100644 index 0000000000000..75eeaf3b5ddc9 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/crashes/route.ts @@ -0,0 +1,151 @@ +/* + * 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 * as t from 'io-ts'; +import { jsonRt, toNumberRt } from '@kbn/io-ts-utils'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; +import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { environmentRt, kueryRt, rangeRt } from '../../default_api_types'; +import { offsetRt } from '../../../../common/comparison_rt'; +import { + getMobileCrashGroupMainStatistics, + MobileCrashGroupMainStatisticsResponse, +} from './get_crash_groups/get_crash_group_main_statistics'; +import { + MobileCrashesGroupPeriodsResponse, + getMobileCrashesGroupPeriods, +} from './get_mobile_crash_group_detailed_statistics'; +import { + CrashDistributionResponse, + getCrashDistribution, +} from './distribution/get_distribution'; + +const mobileCrashDistributionRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + t.partial({ + groupId: t.string, + }), + environmentRt, + kueryRt, + rangeRt, + offsetRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async (resources): Promise => { + const apmEventClient = await getApmEventClient(resources); + const { params } = resources; + const { serviceName } = params.path; + const { environment, kuery, groupId, start, end, offset } = params.query; + return getCrashDistribution({ + environment, + kuery, + serviceName, + groupId, + apmEventClient, + start, + end, + offset, + }); + }, +}); + +const mobileCrashMainStatisticsRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + t.partial({ + sortField: t.string, + sortDirection: t.union([t.literal('asc'), t.literal('desc')]), + }), + environmentRt, + kueryRt, + rangeRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ errorGroups: MobileCrashGroupMainStatisticsResponse }> => { + const { params } = resources; + const apmEventClient = await getApmEventClient(resources); + const { serviceName } = params.path; + const { environment, kuery, sortField, sortDirection, start, end } = + params.query; + + const errorGroups = await getMobileCrashGroupMainStatistics({ + environment, + kuery, + serviceName, + sortField, + sortDirection, + apmEventClient, + start, + end, + }); + + return { errorGroups }; + }, +}); + +const mobileCrashDetailedStatisticsRoute = createApmServerRoute({ + endpoint: + 'POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + offsetRt, + t.type({ + numBuckets: toNumberRt, + }), + ]), + body: t.type({ groupIds: jsonRt.pipe(t.array(t.string)) }), + }), + options: { tags: ['access:apm'] }, + handler: async (resources): Promise => { + const apmEventClient = await getApmEventClient(resources); + const { params } = resources; + + const { + path: { serviceName }, + query: { environment, kuery, numBuckets, start, end, offset }, + body: { groupIds }, + } = params; + + return getMobileCrashesGroupPeriods({ + environment, + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + start, + end, + offset, + }); + }, +}); + +export const mobileCrashRoutes = { + ...mobileCrashDetailedStatisticsRoute, + ...mobileCrashMainStatisticsRoute, + ...mobileCrashDistributionRoute, +}; diff --git a/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_error_group_detailed_statistics.ts new file mode 100644 index 0000000000000..ef7ce97ff9d7e --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_error_group_detailed_statistics.ts @@ -0,0 +1,197 @@ +/* + * 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 { keyBy } from 'lodash'; +import { + rangeQuery, + kqlQuery, + termQuery, + termsQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; +import { Coordinate } from '../../../../typings/timeseries'; +import { ERROR_GROUP_ID, SERVICE_NAME } from '../../../../common/es_fields/apm'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { getBucketSize } from '../../../../common/utils/get_bucket_size'; +import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +interface ErrorGroupDetailedStat { + groupId: string; + timeseries: Coordinate[]; +} + +export async function getMobileErrorGroupDetailedStatistics({ + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + environment, + start, + end, + offset, +}: { + kuery: string; + serviceName: string; + apmEventClient: APMEventClient; + numBuckets: number; + groupIds: string[]; + environment: string; + start: number; + end: number; + offset?: string; +}): Promise { + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const { intervalString } = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + numBuckets, + }); + + const timeseriesResponse = await apmEventClient.search( + 'get_service_error_group_detailed_statistics', + { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termsQuery(ERROR_GROUP_ID, ...groupIds), + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + must_not: { + term: { 'error.type': 'crash' }, + }, + }, + }, + aggs: { + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size: 500, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: startWithOffset, + max: endWithOffset, + }, + }, + }, + }, + }, + }, + }, + } + ); + + if (!timeseriesResponse.aggregations) { + return []; + } + + return timeseriesResponse.aggregations.error_groups.buckets.map((bucket) => { + const groupId = bucket.key as string; + return { + groupId, + timeseries: bucket.timeseries.buckets.map((timeseriesBucket) => { + return { + x: timeseriesBucket.key, + y: timeseriesBucket.doc_count, + }; + }), + }; + }); +} + +export interface MobileErrorGroupPeriodsResponse { + currentPeriod: Record; + previousPeriod: Record; +} + +export async function getMobileErrorGroupPeriods({ + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + environment, + start, + end, + offset, +}: { + kuery: string; + serviceName: string; + apmEventClient: APMEventClient; + numBuckets: number; + groupIds: string[]; + environment: string; + start: number; + end: number; + offset?: string; +}): Promise { + const commonProps = { + environment, + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + }; + + const currentPeriodPromise = getMobileErrorGroupDetailedStatistics({ + ...commonProps, + start, + end, + }); + + const previousPeriodPromise = offset + ? getMobileErrorGroupDetailedStatistics({ + ...commonProps, + start, + end, + offset, + }) + : []; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + + const firstCurrentPeriod = currentPeriod?.[0]; + + return { + currentPeriod: keyBy(currentPeriod, 'groupId'), + previousPeriod: keyBy( + previousPeriod.map((errorRateGroup) => ({ + ...errorRateGroup, + timeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firstCurrentPeriod?.timeseries, + previousPeriodTimeseries: errorRateGroup.timeseries, + }), + })), + 'groupId' + ), + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts new file mode 100644 index 0000000000000..483ff2341d185 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_error_group_main_statistics.ts @@ -0,0 +1,146 @@ +/* + * 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 { AggregationsAggregateOrder } from '@elastic/elasticsearch/lib/api/types'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + ERROR_CULPRIT, + ERROR_EXC_HANDLED, + ERROR_EXC_MESSAGE, + ERROR_EXC_TYPE, + ERROR_GROUP_ID, + ERROR_LOG_MESSAGE, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../common/es_fields/apm'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { getErrorName } from '../../../lib/helpers/get_error_name'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export type MobileErrorGroupMainStatisticsResponse = Array<{ + groupId: string; + name: string; + lastSeen: number; + occurrences: number; + culprit: string | undefined; + handled: boolean | undefined; + type: string | undefined; +}>; + +export async function getMobileErrorGroupMainStatistics({ + kuery, + serviceName, + apmEventClient, + environment, + sortField, + sortDirection = 'desc', + start, + end, + maxNumberOfErrorGroups = 500, + transactionName, + transactionType, +}: { + kuery: string; + serviceName: string; + apmEventClient: APMEventClient; + environment: string; + sortField?: string; + sortDirection?: 'asc' | 'desc'; + start: number; + end: number; + maxNumberOfErrorGroups?: number; + transactionName?: string; + transactionType?: string; +}): Promise { + // sort buckets by last occurrence of error + const sortByLatestOccurrence = sortField === 'lastSeen'; + + const maxTimestampAggKey = 'max_timestamp'; + + const order: AggregationsAggregateOrder = sortByLatestOccurrence + ? { [maxTimestampAggKey]: sortDirection } + : { _count: sortDirection }; + + const response = await apmEventClient.search( + 'get_error_group_main_statistics', + { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + must_not: { + term: { 'error.type': 'crash' }, + }, + filter: [ + ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_NAME, transactionName), + ...termQuery(TRANSACTION_TYPE, transactionType), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size: maxNumberOfErrorGroups, + order, + }, + aggs: { + sample: { + top_hits: { + size: 1, + _source: [ + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, + ERROR_CULPRIT, + ERROR_GROUP_ID, + '@timestamp', + ], + sort: { + '@timestamp': 'desc', + }, + }, + }, + ...(sortByLatestOccurrence + ? { [maxTimestampAggKey]: { max: { field: '@timestamp' } } } + : {}), + }, + }, + }, + }, + } + ); + + return ( + response.aggregations?.error_groups.buckets.map((bucket) => ({ + groupId: bucket.key as string, + name: getErrorName(bucket.sample.hits.hits[0]._source), + lastSeen: new Date( + bucket.sample.hits.hits[0]?._source['@timestamp'] + ).getTime(), + occurrences: bucket.doc_count, + culprit: bucket.sample.hits.hits[0]?._source.error.culprit, + handled: bucket.sample.hits.hits[0]?._source.error.exception?.[0].handled, + type: bucket.sample.hits.hits[0]?._source.error.exception?.[0].type, + })) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_errors_terms_by_field.ts b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_errors_terms_by_field.ts new file mode 100644 index 0000000000000..96cb9bde697a7 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_errors_terms_by_field.ts @@ -0,0 +1,79 @@ +/* + * 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 { + termQuery, + kqlQuery, + rangeQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { SERVICE_NAME } from '../../../../common/es_fields/apm'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export type MobileErrorTermsByFieldResponse = Array<{ + label: string; + count: number; +}>; + +export async function getMobileErrorsTermsByField({ + kuery, + apmEventClient, + serviceName, + environment, + start, + end, + size, + fieldName, +}: { + kuery: string; + apmEventClient: APMEventClient; + serviceName: string; + environment: string; + start: number; + end: number; + size: number; + fieldName: string; +}): Promise { + const response = await apmEventClient.search( + `get_mobile_terms_by_${fieldName}`, + { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + terms: { + terms: { + field: fieldName, + size, + }, + }, + }, + }, + } + ); + + return ( + response.aggregations?.terms?.buckets?.map(({ key, doc_count: count }) => ({ + label: key as string, + count, + })) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_http_errors.ts b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_http_errors.ts new file mode 100644 index 0000000000000..ffd719e8462bb --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/errors/get_mobile_http_errors.ts @@ -0,0 +1,145 @@ +/* + * 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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { + SERVICE_NAME, + HTTP_RESPONSE_STATUS_CODE, +} from '../../../../common/es_fields/apm'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; +import { Coordinate } from '../../../../typings/timeseries'; +import { BUCKET_TARGET_COUNT } from '../../transactions/constants'; + +interface Props { + apmEventClient: APMEventClient; + serviceName: string; + environment: string; + start: number; + end: number; + kuery: string; + offset?: string; +} + +function getBucketSize({ start, end }: { start: number; end: number }) { + return Math.floor((end - start) / BUCKET_TARGET_COUNT); +} + +export interface MobileHttpErrorsTimeseries { + currentPeriod: { timeseries: Coordinate[] }; + previousPeriod: { timeseries: Coordinate[] }; +} +async function getMobileHttpErrorsTimeseries({ + kuery, + apmEventClient, + serviceName, + environment, + start, + end, +}: Props) { + const bucketSize = getBucketSize({ + start, + end, + }); + const response = await apmEventClient.search('get_mobile_http_errors', { + apm: { events: [ProcessorEvent.error] }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(SERVICE_NAME, serviceName), + ...environmentQuery(environment), + ...rangeQuery(start, end), + ...rangeQuery(400, 599, HTTP_RESPONSE_STATUS_CODE), + ...kqlQuery(kuery), + ], + must_not: { + term: { 'error.type': 'crash' }, + }, + }, + }, + aggs: { + timeseries: { + histogram: { + field: '@timestamp', + min_doc_count: 0, + interval: bucketSize, + extended_bounds: { + min: start, + max: end, + }, + }, + }, + }, + }, + }); + + const timeseries = (response?.aggregations?.timeseries.buckets || []).map( + (bucket) => ({ + x: bucket.key, + y: bucket.doc_count, + }) + ); + return { timeseries }; +} + +export async function getMobileHttpErrors({ + kuery, + apmEventClient, + serviceName, + environment, + start, + end, + offset, +}: Props): Promise { + const options = { + serviceName, + apmEventClient, + kuery, + environment, + }; + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const currentPeriodPromise = getMobileHttpErrorsTimeseries({ + ...options, + start, + end, + }); + const previousPeriodPromise = offset + ? getMobileHttpErrorsTimeseries({ + ...options, + start: startWithOffset, + end: endWithOffset, + }) + : { timeseries: [] as Coordinate[] }; + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + return { + currentPeriod, + previousPeriod: { + timeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.timeseries, + previousPeriodTimeseries: previousPeriod.timeseries, + }), + }, + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/errors/route.ts b/x-pack/plugins/apm/server/routes/mobile/errors/route.ts new file mode 100644 index 0000000000000..9a0182891a864 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/errors/route.ts @@ -0,0 +1,197 @@ +/* + * 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. + */ +/* + * 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 * as t from 'io-ts'; +import { jsonRt, toNumberRt } from '@kbn/io-ts-utils'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; +import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { environmentRt, kueryRt, rangeRt } from '../../default_api_types'; +import { offsetRt } from '../../../../common/comparison_rt'; +import { + getMobileErrorGroupPeriods, + MobileErrorGroupPeriodsResponse, +} from './get_mobile_error_group_detailed_statistics'; +import { + MobileErrorGroupMainStatisticsResponse, + getMobileErrorGroupMainStatistics, +} from './get_mobile_error_group_main_statistics'; +import { + getMobileErrorsTermsByField, + MobileErrorTermsByFieldResponse, +} from './get_mobile_errors_terms_by_field'; +import { + MobileHttpErrorsTimeseries, + getMobileHttpErrors, +} from './get_mobile_http_errors'; + +const mobileMobileHttpRatesRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/mobile-services/{serviceName}/error/http_error_rate', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([environmentRt, kueryRt, rangeRt, offsetRt]), + }), + options: { tags: ['access:apm'] }, + handler: async (resources): Promise => { + const apmEventClient = await getApmEventClient(resources); + const { params } = resources; + const { serviceName } = params.path; + const { kuery, environment, start, end, offset } = params.query; + const response = await getMobileHttpErrors({ + kuery, + environment, + start, + end, + serviceName, + apmEventClient, + offset, + }); + + return { ...response }; + }, +}); + +const mobileErrorsDetailedStatisticsRoute = createApmServerRoute({ + endpoint: + 'POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + offsetRt, + t.type({ + numBuckets: toNumberRt, + }), + ]), + body: t.type({ groupIds: jsonRt.pipe(t.array(t.string)) }), + }), + options: { tags: ['access:apm'] }, + handler: async (resources): Promise => { + const apmEventClient = await getApmEventClient(resources); + const { params } = resources; + + const { + path: { serviceName }, + query: { environment, kuery, numBuckets, start, end, offset }, + body: { groupIds }, + } = params; + + return getMobileErrorGroupPeriods({ + environment, + kuery, + serviceName, + apmEventClient, + numBuckets, + groupIds, + start, + end, + offset, + }); + }, +}); + +const mobileErrorTermsByFieldRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/error_terms', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + kueryRt, + rangeRt, + environmentRt, + t.type({ + size: toNumberRt, + fieldName: t.string, + }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + terms: MobileErrorTermsByFieldResponse; + }> => { + const apmEventClient = await getApmEventClient(resources); + const { params } = resources; + const { serviceName } = params.path; + const { kuery, environment, start, end, size, fieldName } = params.query; + const terms = await getMobileErrorsTermsByField({ + kuery, + environment, + start, + end, + serviceName, + apmEventClient, + fieldName, + size, + }); + + return { terms }; + }, +}); + +const mobileErrorsMainStatisticsRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + t.partial({ + sortField: t.string, + sortDirection: t.union([t.literal('asc'), t.literal('desc')]), + }), + environmentRt, + kueryRt, + rangeRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ errorGroups: MobileErrorGroupMainStatisticsResponse }> => { + const { params } = resources; + const apmEventClient = await getApmEventClient(resources); + const { serviceName } = params.path; + const { environment, kuery, sortField, sortDirection, start, end } = + params.query; + + const errorGroups = await getMobileErrorGroupMainStatistics({ + environment, + kuery, + serviceName, + sortField, + sortDirection, + apmEventClient, + start, + end, + }); + + return { errorGroups }; + }, +}); + +export const mobileErrorRoutes = { + ...mobileMobileHttpRatesRoute, + ...mobileErrorsMainStatisticsRoute, + ...mobileErrorsDetailedStatisticsRoute, + ...mobileErrorTermsByFieldRoute, +}; diff --git a/x-pack/plugins/apm/server/routes/mobile/route.ts b/x-pack/plugins/apm/server/routes/mobile/route.ts index e57352b9df3a0..b7bc7d68787b6 100644 --- a/x-pack/plugins/apm/server/routes/mobile/route.ts +++ b/x-pack/plugins/apm/server/routes/mobile/route.ts @@ -39,6 +39,8 @@ import { getMobileMostUsedCharts, MobileMostUsedChartResponse, } from './get_mobile_most_used_charts'; +import { mobileErrorRoutes } from './errors/route'; +import { mobileCrashRoutes } from './crashes/route'; const mobileFiltersRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/mobile/filters', @@ -306,7 +308,6 @@ const mobileTermsByFieldRoute = createApmServerRoute({ const { params } = resources; const { serviceName } = params.path; const { kuery, environment, start, end, size, fieldName } = params.query; - const terms = await getMobileTermsByField({ kuery, environment, @@ -401,6 +402,8 @@ const mobileDetailedStatisticsByField = createApmServerRoute({ }); export const mobileRouteRepository = { + ...mobileErrorRoutes, + ...mobileCrashRoutes, ...mobileFiltersRoute, ...mobileChartsRoute, ...sessionsChartRoute, diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json index 0acdd619492ce..db829dc3ed5f8 100644 --- a/x-pack/plugins/apm/tsconfig.json +++ b/x-pack/plugins/apm/tsconfig.json @@ -105,7 +105,8 @@ "@kbn/deeplinks-observability", "@kbn/custom-icons", "@kbn/elastic-agent-utils", - "@kbn/shared-ux-link-redirect-app" + "@kbn/shared-ux-link-redirect-app", + "@kbn/observability-get-padded-alert-time-range-util" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/index.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/index.ts index 60a917846dc99..b026744f008bd 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/index.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/index.ts @@ -6,3 +6,5 @@ */ export * from './use_cloud_posture_data_table'; +export * from './use_base_es_query'; +export * from './use_persisted_query'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_base_es_query.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_base_es_query.ts new file mode 100644 index 0000000000000..4adffa100e48c --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_base_es_query.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildEsQuery, EsQueryConfig } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { useEffect, useMemo } from 'react'; +import { FindingsBaseESQueryConfig, FindingsBaseProps, FindingsBaseURLQuery } from '../../types'; +import { useKibana } from '../use_kibana'; + +const getBaseQuery = ({ + dataView, + query, + filters, + config, +}: FindingsBaseURLQuery & FindingsBaseProps & FindingsBaseESQueryConfig) => { + try { + return { + query: buildEsQuery(dataView, query, filters, config), // will throw for malformed query + }; + } catch (error) { + return { + query: undefined, + error: error instanceof Error ? error : new Error('Unknown Error'), + }; + } +}; + +export const useBaseEsQuery = ({ + dataView, + filters = [], + query, + nonPersistedFilters, +}: FindingsBaseURLQuery & FindingsBaseProps) => { + const { + notifications: { toasts }, + data: { + query: { filterManager, queryString }, + }, + uiSettings, + } = useKibana().services; + const allowLeadingWildcards = uiSettings.get('query:allowLeadingWildcards'); + const config: EsQueryConfig = useMemo(() => ({ allowLeadingWildcards }), [allowLeadingWildcards]); + const baseEsQuery = useMemo( + () => + getBaseQuery({ + dataView, + filters: filters.concat(nonPersistedFilters ?? []).flat(), + query, + config, + }), + [dataView, filters, nonPersistedFilters, query, config] + ); + + /** + * Sync filters with the URL query + */ + useEffect(() => { + filterManager.setAppFilters(filters); + queryString.setQuery(query); + }, [filters, filterManager, queryString, query]); + + const handleMalformedQueryError = () => { + const error = baseEsQuery instanceof Error ? baseEsQuery : undefined; + if (error) { + toasts.addError(error, { + title: i18n.translate('xpack.csp.findings.search.queryErrorToastMessage', { + defaultMessage: 'Query Error', + }), + toastLifeTimeMs: 1000 * 5, + }); + } + }; + + useEffect(handleMalformedQueryError, [baseEsQuery, toasts]); + + return baseEsQuery; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts index b7b928a208c0a..ae21f45c7a4e8 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts @@ -11,9 +11,11 @@ import { CriteriaWithPagination } from '@elastic/eui'; import { DataTableRecord } from '@kbn/discover-utils/types'; import { useUrlQuery } from '../use_url_query'; import { usePageSize } from '../use_page_size'; -import { getDefaultQuery, useBaseEsQuery, usePersistedQuery } from './utils'; +import { getDefaultQuery } from './utils'; import { LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY } from '../../constants'; import { FindingsBaseURLQuery } from '../../types'; +import { useBaseEsQuery } from './use_base_es_query'; +import { usePersistedQuery } from './use_persisted_query'; type URLQuery = FindingsBaseURLQuery & Record; @@ -140,7 +142,16 @@ export const useCloudPostureDataTable = ({ setUrlQuery, sort: urlQuery.sort, filters: urlQuery.filters, - query: baseEsQuery.query, + query: baseEsQuery.query + ? baseEsQuery.query + : { + bool: { + must: [], + filter: [], + should: [], + must_not: [], + }, + }, queryError, pageIndex: urlQuery.pageIndex, urlQuery, diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_persisted_query.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_persisted_query.ts new file mode 100644 index 0000000000000..c3731c0139ce3 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_persisted_query.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import { type Query } from '@kbn/es-query'; +import { FindingsBaseURLQuery } from '../../types'; +import { useKibana } from '../use_kibana'; + +export const usePersistedQuery = (getter: ({ filters, query }: FindingsBaseURLQuery) => T) => { + const { + data: { + query: { filterManager, queryString }, + }, + } = useKibana().services; + + return useCallback( + () => + getter({ + filters: filterManager.getAppFilters(), + query: queryString.getQuery() as Query, + }), + [getter, filterManager, queryString] + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts index e86d2a77589b0..c715b6a90b4ca 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts @@ -5,32 +5,7 @@ * 2.0. */ -import { useEffect, useCallback, useMemo } from 'react'; -import { buildEsQuery, EsQueryConfig } from '@kbn/es-query'; import type { EuiBasicTableProps, Pagination } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { type Query } from '@kbn/es-query'; -import { useKibana } from '../use_kibana'; -import type { - FindingsBaseESQueryConfig, - FindingsBaseProps, - FindingsBaseURLQuery, -} from '../../types'; - -const getBaseQuery = ({ - dataView, - query, - filters, - config, -}: FindingsBaseURLQuery & FindingsBaseProps & FindingsBaseESQueryConfig) => { - try { - return { - query: buildEsQuery(dataView, query, filters, config), // will throw for malformed query - }; - } catch (error) { - throw new Error(error); - } -}; type TablePagination = NonNullable['pagination']>; @@ -52,74 +27,6 @@ export const getPaginationQuery = ({ size: pageSize, }); -export const useBaseEsQuery = ({ - dataView, - filters = [], - query, - nonPersistedFilters, -}: FindingsBaseURLQuery & FindingsBaseProps) => { - const { - notifications: { toasts }, - data: { - query: { filterManager, queryString }, - }, - uiSettings, - } = useKibana().services; - const allowLeadingWildcards = uiSettings.get('query:allowLeadingWildcards'); - const config: EsQueryConfig = useMemo(() => ({ allowLeadingWildcards }), [allowLeadingWildcards]); - const baseEsQuery = useMemo( - () => - getBaseQuery({ - dataView, - filters: filters.concat(nonPersistedFilters ?? []).flat(), - query, - config, - }), - [dataView, filters, nonPersistedFilters, query, config] - ); - - /** - * Sync filters with the URL query - */ - useEffect(() => { - filterManager.setAppFilters(filters); - queryString.setQuery(query); - }, [filters, filterManager, queryString, query]); - - const handleMalformedQueryError = () => { - const error = baseEsQuery instanceof Error ? baseEsQuery : undefined; - if (error) { - toasts.addError(error, { - title: i18n.translate('xpack.csp.findings.search.queryErrorToastMessage', { - defaultMessage: 'Query Error', - }), - toastLifeTimeMs: 1000 * 5, - }); - } - }; - - useEffect(handleMalformedQueryError, [baseEsQuery, toasts]); - - return baseEsQuery; -}; - -export const usePersistedQuery = (getter: ({ filters, query }: FindingsBaseURLQuery) => T) => { - const { - data: { - query: { filterManager, queryString }, - }, - } = useKibana().services; - - return useCallback( - () => - getter({ - filters: filterManager.getAppFilters(), - query: queryString.getQuery() as Query, - }), - [getter, filterManager, queryString] - ); -}; - export const getDefaultQuery = ({ query, filters }: any): any => ({ query, filters, diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.test.ts new file mode 100644 index 0000000000000..23cf2512ad2cf --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.test.ts @@ -0,0 +1,35 @@ +/* + * 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 { getAbbreviatedNumber } from './get_abbreviated_number'; + +describe('getAbbreviatedNumber', () => { + it('should return the same value if it is less than 1000', () => { + expect(getAbbreviatedNumber(0)).toBe(0); + expect(getAbbreviatedNumber(1)).toBe(1); + expect(getAbbreviatedNumber(500)).toBe(500); + expect(getAbbreviatedNumber(999)).toBe(999); + }); + + it('should use numeral to format the value if it is greater than or equal to 1000', () => { + expect(getAbbreviatedNumber(1000)).toBe('1.0k'); + + expect(getAbbreviatedNumber(1200)).toBe('1.2k'); + + expect(getAbbreviatedNumber(3500000)).toBe('3.5m'); + + expect(getAbbreviatedNumber(2800000000)).toBe('2.8b'); + + expect(getAbbreviatedNumber(5900000000000)).toBe('5.9t'); + + expect(getAbbreviatedNumber(59000000000000000)).toBe('59000.0t'); + }); + + it('should return 0 if the value is NaN', () => { + expect(getAbbreviatedNumber(NaN)).toBe(0); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.ts new file mode 100644 index 0000000000000..353a6482f4706 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.ts @@ -0,0 +1,22 @@ +/* + * 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'; + +/** + * Returns an abbreviated number when the value is greater than or equal to 1000. + * The abbreviated number is formatted using numeral: + * - thousand: k + * - million: m + * - billion: b + * - trillion: t + * */ +export const getAbbreviatedNumber = (value: number) => { + if (isNaN(value)) { + return 0; + } + return value < 1000 ? value : numeral(value).format('0.0a'); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/additional_controls.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/additional_controls.tsx index b1f79779e65e4..ff411d2dcd9e0 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/additional_controls.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/additional_controls.tsx @@ -8,13 +8,9 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiFlexItem } from '@elastic/eui'; import { type DataView } from '@kbn/data-views-plugin/common'; -import numeral from '@elastic/numeral'; import { FieldsSelectorModal } from './fields_selector'; import { useStyles } from './use_styles'; - -const formatNumber = (value: number) => { - return value < 1000 ? value : numeral(value).format('0.0a'); -}; +import { getAbbreviatedNumber } from '../../common/utils/get_abbreviated_number'; const GroupSelectorWrapper: React.FC = ({ children }) => { const styles = useStyles(); @@ -60,7 +56,7 @@ export const AdditionalControls = ({ /> )} - {`${formatNumber(total)} ${title}`} + {`${getAbbreviatedNumber(total)} ${title}`} { `; const groupBySelector = css` - width: 188px; margin-left: auto; `; diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/cloud_security_grouping.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/cloud_security_grouping.tsx index 067046ce5457a..4ec3d3578a541 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/cloud_security_grouping.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/cloud_security_grouping.tsx @@ -9,6 +9,7 @@ import { ParsedGroupingAggregation } from '@kbn/securitysolution-grouping/src'; import { Filter } from '@kbn/es-query'; import React from 'react'; import { css } from '@emotion/react'; +import { CSP_GROUPING, CSP_GROUPING_LOADING } from '../test_subjects'; interface CloudSecurityGroupingProps { data: ParsedGroupingAggregation; @@ -20,8 +21,38 @@ interface CloudSecurityGroupingProps { onChangeGroupsItemsPerPage: (size: number) => void; onChangeGroupsPage: (index: number) => void; selectedGroup: string; + isGroupLoading?: boolean; } +/** + * This component is used to render the loading state of the CloudSecurityGrouping component + * It's used to avoid the flickering of the table when the data is loading + */ +const CloudSecurityGroupingLoading = ({ + grouping, + pageSize, +}: Pick) => { + return ( +
+ {grouping.getGrouping({ + activePage: 0, + data: { + groupsCount: { value: 1 }, + unitsCount: { value: 1 }, + }, + groupingLevel: 0, + inspectButton: undefined, + isLoading: true, + itemsPerPage: pageSize, + renderChildComponent: () => <>, + onGroupClose: () => {}, + selectedGroup: '', + takeActionItems: () => [], + })} +
+ ); +}; + export const CloudSecurityGrouping = ({ data, renderChildComponent, @@ -32,14 +63,22 @@ export const CloudSecurityGrouping = ({ onChangeGroupsItemsPerPage, onChangeGroupsPage, selectedGroup, + isGroupLoading, }: CloudSecurityGroupingProps) => { + if (isGroupLoading) { + return ; + } return (
.euiFlexItem:last-child { display: none; } + && [data-test-subj='group-stats'] > .euiFlexItem:not(:first-child) > span { + border-right: none; + margin-right: 0; + } `} > {grouping.getGrouping({ diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts index d2783af516e35..c59d382144524 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts @@ -4,16 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { isNoneGroup, useGrouping } from '@kbn/securitysolution-grouping'; import * as uuid from 'uuid'; import type { DataView } from '@kbn/data-views-plugin/common'; -import { useUrlQuery } from '../../common/hooks/use_url_query'; import { - useBaseEsQuery, - usePersistedQuery, -} from '../../common/hooks/use_cloud_posture_data_table/utils'; + GroupOption, + GroupPanelRenderer, + GroupStatsRenderer, +} from '@kbn/securitysolution-grouping/src'; +import { useUrlQuery } from '../../common/hooks/use_url_query'; + import { FindingsBaseURLQuery } from '../../common/types'; +import { useBaseEsQuery, usePersistedQuery } from '../../common/hooks/use_cloud_posture_data_table'; const DEFAULT_PAGE_SIZE = 10; const GROUPING_ID = 'cspLatestFindings'; @@ -28,19 +31,23 @@ export const useCloudSecurityGrouping = ({ defaultGroupingOptions, getDefaultQuery, unit, + groupPanelRenderer, + groupStatsRenderer, }: { dataView: DataView; groupingTitle: string; - defaultGroupingOptions: Array<{ label: string; key: string }>; + defaultGroupingOptions: GroupOption[]; getDefaultQuery: (params: FindingsBaseURLQuery) => FindingsBaseURLQuery; unit: (count: number) => string; + groupPanelRenderer?: GroupPanelRenderer; + groupStatsRenderer?: GroupStatsRenderer; }) => { const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); const [activePageIndex, setActivePageIndex] = useState(0); const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); - const { query } = useBaseEsQuery({ + const { query, error } = useBaseEsQuery({ dataView, filters: urlQuery.filters, query: urlQuery.query, @@ -57,6 +64,8 @@ export const useCloudSecurityGrouping = ({ const grouping = useGrouping({ componentProps: { unit, + groupPanelRenderer, + groupStatsRenderer, }, defaultGroupingOptions, fields: dataView.fields, @@ -81,6 +90,16 @@ export const useCloudSecurityGrouping = ({ setPageSize(size); }; + const onResetFilters = useCallback(() => { + setUrlQuery({ + filters: [], + query: { + query: '', + language: 'kuery', + }, + }); + }, [setUrlQuery]); + const onChangeGroupsPage = (index: number) => setActivePageIndex(index); return { @@ -88,11 +107,14 @@ export const useCloudSecurityGrouping = ({ grouping, pageSize, query, + error, selectedGroup, setUrlQuery, uniqueValue, isNoneSelected, onChangeGroupsItemsPerPage, onChangeGroupsPage, + onResetFilters, + filters: urlQuery.filters, }; }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx b/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx index 8f866079ab160..d71d6c2e2384b 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx @@ -6,11 +6,12 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; +import { css, SerializedStyles } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { calculatePostureScore } from '../../common/utils/helpers'; import { statusColors } from '../common/constants'; +import { CSP_FINDINGS_COMPLIANCE_SCORE } from './test_subjects'; /** * This component will take 100% of the width set by the parent @@ -18,20 +19,26 @@ import { statusColors } from '../common/constants'; export const ComplianceScoreBar = ({ totalPassed, totalFailed, + size = 'm', + overrideCss, }: { totalPassed: number; totalFailed: number; + size?: 'm' | 'l'; + overrideCss?: SerializedStyles; }) => { const { euiTheme } = useEuiTheme(); const complianceScore = calculatePostureScore(totalPassed, totalFailed); + // ensures the compliance bar takes full width of its parent + const fullWidthTooltipCss = css` + width: 100%; + `; + return ( - + {!!totalPassed && ( )} {!!totalFailed && ( )} - + {`${complianceScore.toFixed(0)}%`} diff --git a/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts index 91589cee3dcfc..1f603a67ae1fc 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts @@ -39,3 +39,7 @@ export const VULNERABILITIES_CVSS_SCORE_BADGE_SUBJ = 'vulnerabilities_cvss_score export const TAKE_ACTION_SUBJ = 'csp:take_action'; export const CREATE_RULE_ACTION_SUBJ = 'csp:create_rule'; + +export const CSP_GROUPING = 'cloudSecurityGrouping'; +export const CSP_GROUPING_LOADING = 'cloudSecurityGroupingLoading'; +export const CSP_FINDINGS_COMPLIANCE_SCORE = 'cloudSecurityFindingsComplianceScore'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts index 54884856fccf1..e2e4585906bae 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { GroupOption } from '@kbn/securitysolution-grouping'; import { FindingsBaseURLQuery } from '../../../common/types'; import { CloudSecurityDefaultColumn } from '../../../components/cloud_security_data_table'; @@ -15,30 +16,60 @@ export const FINDINGS_UNIT = (totalCount: number) => defaultMessage: `{totalCount, plural, =1 {finding} other {findings}}`, }); -export const defaultGroupingOptions = [ +export const GROUPING_OPTIONS = { + RESOURCE_NAME: 'resource.name', + RULE_NAME: 'rule.name', + CLOUD_ACCOUNT_NAME: 'cloud.account.name', + ORCHESTRATOR_CLUSTER_NAME: 'orchestrator.cluster.name', +}; + +export const NULL_GROUPING_UNIT = i18n.translate('xpack.csp.findings.grouping.nullGroupUnit', { + defaultMessage: 'findings', +}); + +export const NULL_GROUPING_MESSAGES = { + RESOURCE_NAME: i18n.translate('xpack.csp.findings.grouping.resource.nullGroupTitle', { + defaultMessage: 'No resource', + }), + RULE_NAME: i18n.translate('xpack.csp.findings.grouping.rule.nullGroupTitle', { + defaultMessage: 'No rule', + }), + CLOUD_ACCOUNT_NAME: i18n.translate('xpack.csp.findings.grouping.cloudAccount.nullGroupTitle', { + defaultMessage: 'No cloud account', + }), + ORCHESTRATOR_CLUSTER_NAME: i18n.translate( + 'xpack.csp.findings.grouping.kubernetes.nullGroupTitle', + { defaultMessage: 'No Kubernetes cluster' } + ), + DEFAULT: i18n.translate('xpack.csp.findings.grouping.default.nullGroupTitle', { + defaultMessage: 'No grouping', + }), +}; + +export const defaultGroupingOptions: GroupOption[] = [ { label: i18n.translate('xpack.csp.findings.latestFindings.groupByResource', { defaultMessage: 'Resource', }), - key: 'resource.name', + key: GROUPING_OPTIONS.RESOURCE_NAME, }, { label: i18n.translate('xpack.csp.findings.latestFindings.groupByRuleName', { defaultMessage: 'Rule name', }), - key: 'rule.name', + key: GROUPING_OPTIONS.RULE_NAME, }, { label: i18n.translate('xpack.csp.findings.latestFindings.groupByCloudAccount', { defaultMessage: 'Cloud account', }), - key: 'cloud.account.name', + key: GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME, }, { label: i18n.translate('xpack.csp.findings.latestFindings.groupByKubernetesCluster', { defaultMessage: 'Kubernetes cluster', }), - key: 'orchestrator.cluster.name', + key: GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME, }, ]; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_container.tsx index 613594c66e939..9d536f0f0b180 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_container.tsx @@ -6,13 +6,17 @@ */ import React, { useCallback } from 'react'; import { Filter } from '@kbn/es-query'; -import { defaultLoadingRenderer } from '../../../components/cloud_posture_page'; +import { EuiSpacer } from '@elastic/eui'; +import { EmptyState } from '../../../components/empty_state'; import { CloudSecurityGrouping } from '../../../components/cloud_security_grouping'; import type { FindingsBaseProps } from '../../../common/types'; import { FindingsSearchBar } from '../layout/findings_search_bar'; import { DEFAULT_TABLE_HEIGHT } from './constants'; import { useLatestFindingsGrouping } from './use_latest_findings_grouping'; import { LatestFindingsTable } from './latest_findings_table'; +import { groupPanelRenderer, groupStatsRenderer } from './latest_findings_group_renderer'; +import { FindingsDistributionBar } from '../layout/findings_distribution_bar'; +import { ErrorCallout } from '../layout/error_callout'; export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { const renderChildComponent = useCallback( @@ -30,7 +34,7 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { ); const { - isGroupSelect, + isGroupSelected, groupData, grouping, isFetching, @@ -41,26 +45,49 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { onChangeGroupsPage, setUrlQuery, isGroupLoading, - } = useLatestFindingsGrouping({ dataView }); + onResetFilters, + error, + totalPassedFindings, + onDistributionBarClick, + totalFailedFindings, + isEmptyResults, + } = useLatestFindingsGrouping({ dataView, groupPanelRenderer, groupStatsRenderer }); - if (isGroupSelect) { - return isGroupLoading ? ( - defaultLoadingRenderer() - ) : ( -
+ if (error || isEmptyResults) { + return ( + <> - -
+ + {error && } + {isEmptyResults && } + + ); + } + if (isGroupSelected) { + return ( + <> + +
+ + + +
+ ); } diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx new file mode 100644 index 0000000000000..d0684452fb23a --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx @@ -0,0 +1,312 @@ +/* + * 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 { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiSkeletonTitle, + EuiText, + EuiTextBlockTruncate, + EuiToolTip, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { + ECSField, + GroupPanelRenderer, + RawBucket, + StatRenderer, +} from '@kbn/securitysolution-grouping/src'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { getAbbreviatedNumber } from '../../../common/utils/get_abbreviated_number'; +import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon'; +import { ComplianceScoreBar } from '../../../components/compliance_score_bar'; +import { FindingsGroupingAggregation } from './use_grouped_findings'; +import { GROUPING_OPTIONS, NULL_GROUPING_MESSAGES, NULL_GROUPING_UNIT } from './constants'; +import { FINDINGS_GROUPING_COUNTER } from '../test_subjects'; + +/** + * Return first non-null value. If the field contains an array, this will return the first value that isn't null. If the field isn't an array it'll be returned unless it's null. + */ +export function firstNonNullValue(valueOrCollection: ECSField): T | undefined { + if (valueOrCollection === null) { + return undefined; + } else if (Array.isArray(valueOrCollection)) { + for (const value of valueOrCollection) { + if (value !== null) { + return value; + } + } + } else { + return valueOrCollection; + } +} + +const NullGroupComponent = ({ + title, + field, + unit = NULL_GROUPING_UNIT, +}: { + title: string; + field: string; + unit?: string; +}) => { + return ( + + {title} + + + + + ), + field: {field}, + unit, + }} + /> + + } + position="right" + /> + + ); +}; + +export const groupPanelRenderer: GroupPanelRenderer = ( + selectedGroup, + bucket, + nullGroupMessage, + isLoading +) => { + if (isLoading) { + return ( + + + + ); + } + const benchmarkId = firstNonNullValue(bucket.benchmarkId?.buckets?.[0]?.key); + switch (selectedGroup) { + case GROUPING_OPTIONS.RESOURCE_NAME: + return nullGroupMessage ? ( + + ) : ( + + + + + + + {bucket.key_as_string} {bucket.resourceName?.buckets?.[0].key} + + + + + + {bucket.resourceSubType?.buckets?.[0].key} + + + + + + ); + case GROUPING_OPTIONS.RULE_NAME: + return nullGroupMessage ? ( + + ) : ( + + + + + + {bucket.key_as_string} + + + + + {firstNonNullValue(bucket.benchmarkName?.buckets?.[0].key)}{' '} + {firstNonNullValue(bucket.benchmarkVersion?.buckets?.[0].key)} + + + + + + ); + case GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME: + return nullGroupMessage ? ( + + ) : ( + + {benchmarkId && ( + + + + )} + + + + + {bucket.key_as_string} + + + + + {bucket.benchmarkName?.buckets?.[0]?.key} + + + + + + ); + case GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME: + return nullGroupMessage ? ( + + ) : ( + + {benchmarkId && ( + + + + )} + + + + + {bucket.key_as_string} + + + + + {bucket.benchmarkName?.buckets?.[0]?.key} + + + + + + ); + default: + return nullGroupMessage ? ( + + ) : ( + + + + + + {bucket.key_as_string} + + + + + + ); + } +}; + +const FindingsCountComponent = ({ bucket }: { bucket: RawBucket }) => { + const { euiTheme } = useEuiTheme(); + + return ( + + + {getAbbreviatedNumber(bucket.doc_count)} + + + ); +}; + +const FindingsCount = React.memo(FindingsCountComponent); + +const ComplianceBarComponent = ({ bucket }: { bucket: RawBucket }) => { + const { euiTheme } = useEuiTheme(); + + const totalFailed = bucket.failedFindings?.doc_count || 0; + const totalPassed = bucket.doc_count - totalFailed; + return ( + + ); +}; + +const ComplianceBar = React.memo(ComplianceBarComponent); + +export const groupStatsRenderer = ( + selectedGroup: string, + bucket: RawBucket +): StatRenderer[] => { + const defaultBadges = [ + { + title: i18n.translate('xpack.csp.findings.grouping.stats.badges.findings', { + defaultMessage: 'Findings', + }), + renderer: , + }, + { + title: i18n.translate('xpack.csp.findings.grouping.stats.badges.compliance', { + defaultMessage: 'Compliance', + }), + renderer: , + }, + ]; + + return defaultBadges; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx index a66ce54fa99cc..be6d34a1df933 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx @@ -123,7 +123,7 @@ export const LatestFindingsTable = ({ passed={passed} failed={failed} /> - + )} { + failedFindings?: { + doc_count?: NumberOrNull; + }; + passedFindings?: { + doc_count?: NumberOrNull; + }; +} + export interface FindingsGroupingAggregation { unitsCount?: { value?: NumberOrNull; @@ -25,9 +34,37 @@ export interface FindingsGroupingAggregation { groupsCount?: { value?: NumberOrNull; }; + failedFindings?: { + doc_count?: NumberOrNull; + }; + passedFindings?: { + doc_count?: NumberOrNull; + }; groupByFields?: { buckets?: GenericBuckets[]; }; + description?: { + buckets?: GenericBuckets[]; + }; + resourceName?: { + buckets?: GenericBuckets[]; + }; + resourceSubType?: { + buckets?: GenericBuckets[]; + }; + resourceType?: { + buckets?: GenericBuckets[]; + }; + benchmarkName?: { + buckets?: GenericBuckets[]; + }; + benchmarkVersion?: { + buckets?: GenericBuckets[]; + }; + benchmarkId?: { + buckets?: GenericBuckets[]; + }; + isLoading?: boolean; } export const getGroupedFindingsQuery = (query: GroupingQuery) => ({ @@ -56,9 +93,7 @@ export const useGroupedFindings = ({ } = await lastValueFrom( data.search.search< {}, - IKibanaSearchResponse< - SearchResponse<{}, GroupingAggregation> - > + IKibanaSearchResponse> >({ params: getGroupedFindingsQuery(query), }) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx index 137716967460f..be5253cc710b7 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx @@ -5,19 +5,131 @@ * 2.0. */ import { getGroupingQuery } from '@kbn/securitysolution-grouping'; -import { parseGroupingQuery } from '@kbn/securitysolution-grouping/src'; +import { + GroupingAggregation, + GroupPanelRenderer, + GroupStatsRenderer, + isNoneGroup, + NamedAggregation, + parseGroupingQuery, +} from '@kbn/securitysolution-grouping/src'; import { useMemo } from 'react'; import { DataView } from '@kbn/data-views-plugin/common'; +import { Evaluation } from '../../../../common/types'; import { LATEST_FINDINGS_RETENTION_POLICY } from '../../../../common/constants'; -import { useGroupedFindings } from './use_grouped_findings'; -import { FINDINGS_UNIT, groupingTitle, defaultGroupingOptions, getDefaultQuery } from './constants'; +import { + FindingsGroupingAggregation, + FindingsRootGroupingAggregation, + useGroupedFindings, +} from './use_grouped_findings'; +import { + FINDINGS_UNIT, + groupingTitle, + defaultGroupingOptions, + getDefaultQuery, + GROUPING_OPTIONS, +} from './constants'; import { useCloudSecurityGrouping } from '../../../components/cloud_security_grouping'; +import { getFilters } from '../utils/get_filters'; + +const getTermAggregation = (key: keyof FindingsGroupingAggregation, field: string) => ({ + [key]: { + terms: { field, size: 1 }, + }, +}); + +const getAggregationsByGroupField = (field: string): NamedAggregation[] => { + if (isNoneGroup([field])) { + return []; + } + const aggMetrics: NamedAggregation[] = [ + { + groupByField: { + cardinality: { + field, + }, + }, + failedFindings: { + filter: { + term: { + 'result.evaluation': { value: 'failed' }, + }, + }, + }, + passedFindings: { + filter: { + term: { + 'result.evaluation': { value: 'passed' }, + }, + }, + }, + complianceScore: { + bucket_script: { + buckets_path: { + passed: 'passedFindings>_count', + failed: 'failedFindings>_count', + }, + script: 'params.passed / (params.passed + params.failed)', + }, + }, + }, + ]; + + switch (field) { + case GROUPING_OPTIONS.RESOURCE_NAME: + return [ + ...aggMetrics, + getTermAggregation('resourceName', 'resource.id'), + getTermAggregation('resourceSubType', 'resource.sub_type'), + getTermAggregation('resourceType', 'resource.type'), + ]; + case GROUPING_OPTIONS.RULE_NAME: + return [ + ...aggMetrics, + getTermAggregation('benchmarkName', 'rule.benchmark.name'), + getTermAggregation('benchmarkVersion', 'rule.benchmark.version'), + ]; + case GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME: + return [ + ...aggMetrics, + getTermAggregation('benchmarkName', 'rule.benchmark.name'), + getTermAggregation('benchmarkId', 'rule.benchmark.id'), + ]; + case GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME: + return [ + ...aggMetrics, + getTermAggregation('benchmarkName', 'rule.benchmark.name'), + getTermAggregation('benchmarkId', 'rule.benchmark.id'), + ]; + } + return aggMetrics; +}; + +/** + * Type Guard for checking if the given source is a FindingsRootGroupingAggregation + */ +export const isFindingsRootGroupingAggregation = ( + groupData: Record | undefined +): groupData is FindingsRootGroupingAggregation => { + return ( + groupData?.passedFindings?.doc_count !== undefined && + groupData?.failedFindings?.doc_count !== undefined + ); +}; /** * Utility hook to get the latest findings grouping data * for the findings page */ -export const useLatestFindingsGrouping = ({ dataView }: { dataView: DataView }) => { +export const useLatestFindingsGrouping = ({ + dataView, + groupPanelRenderer, + groupStatsRenderer, +}: { + dataView: DataView; + groupPanelRenderer?: GroupPanelRenderer; + groupStatsRenderer?: GroupStatsRenderer; +}) => { const { activePageIndex, grouping, @@ -29,23 +141,47 @@ export const useLatestFindingsGrouping = ({ dataView }: { dataView: DataView }) setUrlQuery, uniqueValue, isNoneSelected, + onResetFilters, + error, + filters, } = useCloudSecurityGrouping({ dataView, groupingTitle, defaultGroupingOptions, getDefaultQuery, unit: FINDINGS_UNIT, + groupPanelRenderer, + groupStatsRenderer, }); const groupingQuery = getGroupingQuery({ - additionalFilters: [query], + additionalFilters: query ? [query] : [], groupByField: selectedGroup, uniqueValue, from: `now-${LATEST_FINDINGS_RETENTION_POLICY}`, to: 'now', pageNumber: activePageIndex * pageSize, size: pageSize, - sort: [{ _key: { order: 'asc' } }], + sort: [{ groupByField: { order: 'desc' } }, { complianceScore: { order: 'asc' } }], + statsAggregations: getAggregationsByGroupField(selectedGroup), + rootAggregations: [ + { + failedFindings: { + filter: { + term: { + 'result.evaluation': { value: 'failed' }, + }, + }, + }, + passedFindings: { + filter: { + term: { + 'result.evaluation': { value: 'passed' }, + }, + }, + }, + }, + ], }); const { data, isFetching } = useGroupedFindings({ @@ -54,10 +190,37 @@ export const useLatestFindingsGrouping = ({ dataView }: { dataView: DataView }) }); const groupData = useMemo( - () => parseGroupingQuery(selectedGroup, uniqueValue, data), + () => + parseGroupingQuery( + selectedGroup, + uniqueValue, + data as GroupingAggregation + ), [data, selectedGroup, uniqueValue] ); + const totalPassedFindings = isFindingsRootGroupingAggregation(groupData) + ? groupData?.passedFindings?.doc_count || 0 + : 0; + const totalFailedFindings = isFindingsRootGroupingAggregation(groupData) + ? groupData?.failedFindings?.doc_count || 0 + : 0; + + const onDistributionBarClick = (evaluation: Evaluation) => { + setUrlQuery({ + filters: getFilters({ + filters, + dataView, + field: 'result.evaluation', + value: evaluation, + negate: false, + }), + }); + }; + + const isEmptyResults = + !isFetching && isFindingsRootGroupingAggregation(groupData) && !groupData.unitsCount?.value; + return { groupData, grouping, @@ -68,7 +231,14 @@ export const useLatestFindingsGrouping = ({ dataView }: { dataView: DataView }) onChangeGroupsItemsPerPage, onChangeGroupsPage, setUrlQuery, - isGroupSelect: !isNoneSelected, + isGroupSelected: !isNoneSelected, isGroupLoading: !data, + onResetFilters, + filters, + error, + onDistributionBarClick, + totalPassedFindings, + totalFailedFindings, + isEmptyResults, }; }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_container.tsx index 7f483c3ee0847..3054ad352a5bd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_container.tsx @@ -39,6 +39,9 @@ const getDefaultQuery = ({ sort: { field: 'compliance_score' as keyof CspFinding, direction: 'asc' }, }); +/** + * @deprecated: This component is deprecated and will be removed in the next release. + */ export const FindingsByResourceContainer = ({ dataView }: FindingsBaseProps) => ( ); +/** + * @deprecated: This component is deprecated and will be removed in the next release. + */ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { const { queryError, query, pageSize, setTableOptions, urlQuery, setUrlQuery, onResetFilters } = useCloudPostureTable({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx index ce3f55e03417d..71c4219d3b852 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx @@ -29,6 +29,10 @@ import { } from '../layout/findings_layout'; import { EmptyState } from '../../../components/empty_state'; +/** + * @deprecated: This function is deprecated and will be removed in the next release. + * use getAbbreviatedNumber from x-pack/plugins/cloud_security_posture/public/common/utils/get_abbreviated_number.ts + */ export const formatNumber = (value: number) => value < 1000 ? value : numeral(value).format('0.0a'); @@ -44,11 +48,17 @@ interface Props { onResetFilters: () => void; } +/** + * @deprecated: This function is deprecated and will be removed in the next release. + */ export const getResourceId = (resource: FindingsByResourcePage) => { const sections = resource['rule.section'] || []; return [resource.resource_id, ...sections].join('/'); }; +/** + * @deprecated: This component is deprecated and will be removed in the next release. + */ const FindingsByResourceTableComponent = ({ items, loading, @@ -189,8 +199,14 @@ const baseColumns: Array> = type BaseFindingColumnName = typeof baseColumns[number]['field']; +/** + * @deprecated: This function is deprecated and will be removed in the next release. + */ export const findingsByResourceColumns = Object.fromEntries( baseColumns.map((column) => [column.field, column]) ) as Record; +/** + * @deprecated: This component is deprecated and will be removed in the next release. + */ export const FindingsByResourceTable = React.memo(FindingsByResourceTableComponent); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 83182d66665d7..f606241862ee2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -95,6 +95,9 @@ const getResourceFindingSharedValues = (sharedValues: { }, ]; +/** + * @deprecated: This component is deprecated and will be removed in the next release. + */ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const params = useParams<{ resourceId: string }>(); const decodedResourceId = decodeURIComponent(params.resourceId); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_table.tsx index 09f046f504ea7..4dd7070af88f1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/resource_findings_table.tsx @@ -106,4 +106,7 @@ const ResourceFindingsTableComponent = ({ ); }; +/** + * @deprecated: This component is deprecated and will be removed in the next release. + */ export const ResourceFindingsTable = React.memo(ResourceFindingsTableComponent); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/use_resource_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/use_resource_findings.ts index cd1a3484548c7..46a5e12665660 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/use_resource_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/resource_findings/use_resource_findings.ts @@ -80,6 +80,9 @@ const getResourceFindingsQuery = ({ ignore_unavailable: false, }); +/** + * @deprecated: This hook is deprecated and will be removed in the next release. + */ export const useResourceFindings = (options: UseResourceFindingsOptions) => { const { data, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/use_findings_by_resource.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/use_findings_by_resource.ts index d0253966bc87c..e4bbd955f6092 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/use_findings_by_resource.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/use_findings_by_resource.ts @@ -76,6 +76,9 @@ interface FindingsAggBucket extends AggregationsStringRareTermsBucketKeys { cis_sections: AggregationsMultiBucketAggregateBase; } +/** + * @deprecated: This hook is deprecated and will be removed in the next release. + */ export const getFindingsByResourceAggQuery = ({ query, sortDirection, @@ -168,6 +171,9 @@ const createFindingsByResource = (resource: FindingsAggBucket): FindingsByResour }, }); +/** + * @deprecated: This hook is deprecated and will be removed in the next release. + */ export const useFindingsByResource = (options: UseFindingsByResourceOptions) => { const { data, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx index 294b05d9dd802..1cf084220ea29 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import numeral from '@elastic/numeral'; +import { getAbbreviatedNumber } from '../../../common/utils/get_abbreviated_number'; import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; import { statusColors } from '../../../common/constants'; import type { Evaluation } from '../../../../common/types'; @@ -28,8 +28,6 @@ interface Props { distributionOnClick: (evaluation: Evaluation) => void; } -const formatNumber = (value: number) => (value < 1000 ? value : numeral(value).format('0.0a')); - export const CurrentPageOfTotal = ({ pageEnd, pageStart, @@ -48,7 +46,7 @@ export const CurrentPageOfTotal = ({ values={{ pageStart: {pageStart}, pageEnd: {pageEnd}, - total: {formatNumber(total)}, + total: {getAbbreviatedNumber(total)}, type, }} /> @@ -113,7 +111,7 @@ const DistributionBar: React.FC> = ({ gutterSize="none" css={css` height: 8px; - background: ${euiTheme.colors.subduedText}; + background: ${euiTheme.colors.lightestShade}; `} > {label} - {formatNumber(value)} + {getAbbreviatedNumber(value)}
); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts index b0d5a8ffd19f5..b465b58f45887 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts @@ -20,6 +20,7 @@ export const LATEST_FINDINGS_CONTAINER = 'latest_findings_container'; export const LATEST_FINDINGS_TABLE = 'latest_findings_table'; export const FINDINGS_GROUP_BY_SELECTOR = 'findings_group_by_selector'; +export const FINDINGS_GROUPING_COUNTER = 'findings_grouping_counter'; export const getFindingsTableRowTestId = (id: string) => `findings_table_row_${id}`; export const getFindingsTableCellTestId = (columnId: string, rowId: string) => diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx index 966f95cb367fa..67e46d82020cc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx @@ -99,16 +99,6 @@ export const Findings = () => { - - - { defaultMessage="Misconfigurations" /> + + + )} diff --git a/x-pack/plugins/elastic_assistant/server/plugin.ts b/x-pack/plugins/elastic_assistant/server/plugin.ts index 827b428c97803..a0df339695885 100755 --- a/x-pack/plugins/elastic_assistant/server/plugin.ts +++ b/x-pack/plugins/elastic_assistant/server/plugin.ts @@ -80,7 +80,7 @@ export class ElasticAssistantPlugin const getElserId: GetElser = once( async (request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) => { return (await plugins.ml.trainedModelsProvider(request, savedObjectsClient).getELSER()) - .name; + .model_id; } ); diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts index f5f7945376d95..78af0a862d302 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts @@ -6,7 +6,7 @@ */ import { MlTrainedModelConfig, MlTrainedModelStats } from '@elastic/elasticsearch/lib/api/types'; -import { BUILT_IN_MODEL_TAG } from '@kbn/ml-trained-models-utils'; +import { BUILT_IN_MODEL_TAG, TRAINED_MODEL_TYPE } from '@kbn/ml-trained-models-utils'; import { MlInferencePipeline, TrainedModelState } from '../types/pipelines'; @@ -14,6 +14,7 @@ import { generateMlInferencePipelineBody, getMlModelTypesForModelConfig, parseMlInferenceParametersFromPipeline, + parseModelState, parseModelStateFromStats, parseModelStateReasonFromStats, } from '.'; @@ -265,8 +266,12 @@ describe('parseMlInferenceParametersFromPipeline', () => { }); describe('parseModelStateFromStats', () => { - it('returns not deployed for undefined stats', () => { - expect(parseModelStateFromStats()).toEqual(TrainedModelState.NotDeployed); + it('returns Started for the lang_ident model', () => { + expect( + parseModelStateFromStats({ + model_type: TRAINED_MODEL_TYPE.LANG_IDENT, + }) + ).toEqual(TrainedModelState.Started); }); it('returns Started', () => { expect( @@ -315,6 +320,28 @@ describe('parseModelStateFromStats', () => { }); }); +describe('parseModelState', () => { + it('returns Started', () => { + expect(parseModelState('started')).toEqual(TrainedModelState.Started); + expect(parseModelState('fully_allocated')).toEqual(TrainedModelState.Started); + }); + it('returns Starting', () => { + expect(parseModelState('starting')).toEqual(TrainedModelState.Starting); + expect(parseModelState('downloading')).toEqual(TrainedModelState.Starting); + expect(parseModelState('downloaded')).toEqual(TrainedModelState.Starting); + }); + it('returns Stopping', () => { + expect(parseModelState('stopping')).toEqual(TrainedModelState.Stopping); + }); + it('returns Failed', () => { + expect(parseModelState('failed')).toEqual(TrainedModelState.Failed); + }); + it('returns NotDeployed for an unknown state', () => { + expect(parseModelState(undefined)).toEqual(TrainedModelState.NotDeployed); + expect(parseModelState('other_state')).toEqual(TrainedModelState.NotDeployed); + }); +}); + describe('parseModelStateReasonFromStats', () => { it('returns reason from deployment_stats', () => { const reason = 'This is the reason the model is in a failed state'; diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts index 95c6672df6928..5f56c1105b297 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts @@ -202,10 +202,18 @@ export const parseModelStateFromStats = ( modelTypes?.includes(TRAINED_MODEL_TYPE.LANG_IDENT) ) return TrainedModelState.Started; - switch (model?.deployment_stats?.state) { + + return parseModelState(model?.deployment_stats?.state); +}; + +export const parseModelState = (state?: string) => { + switch (state) { case 'started': + case 'fully_allocated': return TrainedModelState.Started; case 'starting': + case 'downloading': + case 'downloaded': return TrainedModelState.Starting; case 'stopping': return TrainedModelState.Stopping; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx index 444fd87ef4160..e3f771d0ba7e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { useValues, useActions } from 'kea'; import { + EuiCallOut, EuiFieldText, EuiForm, EuiFormRow, @@ -24,15 +25,13 @@ import { import { i18n } from '@kbn/i18n'; -import { IndexNameLogic } from '../../index_name_logic'; import { IndexViewLogic } from '../../index_view_logic'; import { EMPTY_PIPELINE_CONFIGURATION, MLInferenceLogic } from './ml_inference_logic'; -import { MlModelSelectOption } from './model_select_option'; +import { ModelSelect } from './model_select'; +import { ModelSelectLogic } from './model_select_logic'; import { PipelineSelectOption } from './pipeline_select_option'; -import { MODEL_REDACTED_VALUE, MODEL_SELECT_PLACEHOLDER, normalizeModelName } from './utils'; -const MODEL_SELECT_PLACEHOLDER_VALUE = 'model_placeholder$$'; const PIPELINE_SELECT_PLACEHOLDER_VALUE = 'pipeline_placeholder$$'; const CREATE_NEW_TAB_NAME = i18n.translate( @@ -55,32 +54,15 @@ export const ConfigurePipeline: React.FC = () => { addInferencePipelineModal: { configuration }, formErrors, existingInferencePipelines, - supportedMLModels, } = useValues(MLInferenceLogic); const { selectExistingPipeline, setInferencePipelineConfiguration } = useActions(MLInferenceLogic); const { ingestionMethod } = useValues(IndexViewLogic); - const { indexName } = useValues(IndexNameLogic); - - const { existingPipeline, modelID, pipelineName, isPipelineNameUserSupplied } = configuration; + const { modelStateChangeError } = useValues(ModelSelectLogic); + const { pipelineName } = configuration; const nameError = formErrors.pipelineName !== undefined && pipelineName.length > 0; - const modelOptions: Array> = [ - { - disabled: true, - inputDisplay: - existingPipeline && pipelineName.length > 0 - ? MODEL_REDACTED_VALUE - : MODEL_SELECT_PLACEHOLDER, - value: MODEL_SELECT_PLACEHOLDER_VALUE, - }, - ...supportedMLModels.map((model) => ({ - dropdownDisplay: , - inputDisplay: model.model_id, - value: model.model_id, - })), - ]; const pipelineOptions: Array> = [ { disabled: true, @@ -154,6 +136,22 @@ export const ConfigurePipeline: React.FC = () => { } /> + {modelStateChangeError && ( + <> + + + {modelStateChangeError} + + + + )} { { defaultMessage: 'Select a trained ML Model' } )} > - - setInferencePipelineConfiguration({ - ...configuration, - inferenceConfig: undefined, - modelID: value, - fieldMappings: undefined, - pipelineName: isPipelineNameUserSupplied - ? pipelineName - : indexName + '-' + normalizeModelName(value), - }) - } - options={modelOptions} - valueOfSelected={modelID === '' ? MODEL_SELECT_PLACEHOLDER_VALUE : modelID} - /> + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select.test.tsx new file mode 100644 index 0000000000000..15fb492fae56d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select.test.tsx @@ -0,0 +1,136 @@ +/* + * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiSelectable } from '@elastic/eui'; + +import { ModelSelect } from './model_select'; + +const DEFAULT_VALUES = { + addInferencePipelineModal: { + configuration: {}, + }, + selectableModels: [ + { + modelId: 'model_1', + }, + { + modelId: 'model_2', + }, + ], + indexName: 'my-index', +}; +const MOCK_ACTIONS = { + setInferencePipelineConfiguration: jest.fn(), +}; + +describe('ModelSelect', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({}); + setMockActions(MOCK_ACTIONS); + }); + it('renders model select with no options', () => { + setMockValues({ + ...DEFAULT_VALUES, + selectableModels: null, + }); + + const wrapper = shallow(); + expect(wrapper.find(EuiSelectable)).toHaveLength(1); + const selectable = wrapper.find(EuiSelectable); + expect(selectable.prop('options')).toEqual([]); + }); + it('renders model select with options', () => { + setMockValues(DEFAULT_VALUES); + + const wrapper = shallow(); + expect(wrapper.find(EuiSelectable)).toHaveLength(1); + const selectable = wrapper.find(EuiSelectable); + expect(selectable.prop('options')).toEqual([ + { + modelId: 'model_1', + label: 'model_1', + }, + { + modelId: 'model_2', + label: 'model_2', + }, + ]); + }); + it('selects the chosen option', () => { + setMockValues({ + ...DEFAULT_VALUES, + addInferencePipelineModal: { + configuration: { + ...DEFAULT_VALUES.addInferencePipelineModal.configuration, + modelID: 'model_2', + }, + }, + }); + + const wrapper = shallow(); + expect(wrapper.find(EuiSelectable)).toHaveLength(1); + const selectable = wrapper.find(EuiSelectable); + expect(selectable.prop('options')[1].checked).toEqual('on'); + }); + it('sets model ID on selecting an item and clears config', () => { + setMockValues(DEFAULT_VALUES); + + const wrapper = shallow(); + expect(wrapper.find(EuiSelectable)).toHaveLength(1); + const selectable = wrapper.find(EuiSelectable); + selectable.simulate('change', [{ modelId: 'model_1' }, { modelId: 'model_2', checked: 'on' }]); + expect(MOCK_ACTIONS.setInferencePipelineConfiguration).toHaveBeenCalledWith( + expect.objectContaining({ + inferenceConfig: undefined, + modelID: 'model_2', + fieldMappings: undefined, + }) + ); + }); + it('generates pipeline name on selecting an item', () => { + setMockValues(DEFAULT_VALUES); + + const wrapper = shallow(); + expect(wrapper.find(EuiSelectable)).toHaveLength(1); + const selectable = wrapper.find(EuiSelectable); + selectable.simulate('change', [{ modelId: 'model_1' }, { modelId: 'model_2', checked: 'on' }]); + expect(MOCK_ACTIONS.setInferencePipelineConfiguration).toHaveBeenCalledWith( + expect.objectContaining({ + pipelineName: 'my-index-model_2', + }) + ); + }); + it('does not generate pipeline name on selecting an item if it a name was supplied by the user', () => { + setMockValues({ + ...DEFAULT_VALUES, + addInferencePipelineModal: { + configuration: { + ...DEFAULT_VALUES.addInferencePipelineModal.configuration, + pipelineName: 'user-pipeline', + isPipelineNameUserSupplied: true, + }, + }, + }); + + const wrapper = shallow(); + expect(wrapper.find(EuiSelectable)).toHaveLength(1); + const selectable = wrapper.find(EuiSelectable); + selectable.simulate('change', [{ modelId: 'model_1' }, { modelId: 'model_2', checked: 'on' }]); + expect(MOCK_ACTIONS.setInferencePipelineConfiguration).toHaveBeenCalledWith( + expect.objectContaining({ + pipelineName: 'user-pipeline', + }) + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select.tsx new file mode 100644 index 0000000000000..86c91c483702f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiSelectable, useIsWithinMaxBreakpoint } from '@elastic/eui'; + +import { MlModel } from '../../../../../../../common/types/ml'; +import { IndexNameLogic } from '../../index_name_logic'; +import { IndexViewLogic } from '../../index_view_logic'; + +import { MLInferenceLogic } from './ml_inference_logic'; +import { ModelSelectLogic } from './model_select_logic'; +import { ModelSelectOption, ModelSelectOptionProps } from './model_select_option'; +import { normalizeModelName } from './utils'; + +export const ModelSelect: React.FC = () => { + const { indexName } = useValues(IndexNameLogic); + const { ingestionMethod } = useValues(IndexViewLogic); + const { + addInferencePipelineModal: { configuration }, + } = useValues(MLInferenceLogic); + const { selectableModels, isLoading } = useValues(ModelSelectLogic); + const { setInferencePipelineConfiguration } = useActions(MLInferenceLogic); + + const { modelID, pipelineName, isPipelineNameUserSupplied } = configuration; + + const getModelSelectOptionProps = (models: MlModel[]): ModelSelectOptionProps[] => + (models ?? []).map((model) => ({ + ...model, + label: model.modelId, + checked: model.modelId === modelID ? 'on' : undefined, + })); + + const onChange = (options: ModelSelectOptionProps[]) => { + const selectedOption = options.find((option) => option.checked === 'on'); + setInferencePipelineConfiguration({ + ...configuration, + inferenceConfig: undefined, + modelID: selectedOption?.modelId ?? '', + fieldMappings: undefined, + pipelineName: isPipelineNameUserSupplied + ? pipelineName + : indexName + '-' + normalizeModelName(selectedOption?.modelId ?? ''), + }); + }; + + const renderOption = (option: ModelSelectOptionProps) => ; + + return ( + + {(list, search) => ( + <> + {search} + {list} + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_logic.test.ts new file mode 100644 index 0000000000000..1252d77bb776a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_logic.test.ts @@ -0,0 +1,160 @@ +/* + * 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 { LogicMounter } from '../../../../../__mocks__/kea_logic'; + +import { HttpError } from '../../../../../../../common/types/api'; +import { MlModel, MlModelDeploymentState } from '../../../../../../../common/types/ml'; +import { CachedFetchModelsApiLogic } from '../../../../api/ml_models/cached_fetch_models_api_logic'; +import { + CreateModelApiLogic, + CreateModelResponse, +} from '../../../../api/ml_models/create_model_api_logic'; +import { StartModelApiLogic } from '../../../../api/ml_models/start_model_api_logic'; + +import { ModelSelectLogic } from './model_select_logic'; + +const CREATE_MODEL_API_RESPONSE: CreateModelResponse = { + modelId: 'model_1', + deploymentState: MlModelDeploymentState.NotDeployed, +}; +const FETCH_MODELS_API_DATA_RESPONSE: MlModel[] = [ + { + modelId: 'model_1', + title: 'Model 1', + type: 'ner', + deploymentState: MlModelDeploymentState.NotDeployed, + startTime: 0, + targetAllocationCount: 0, + nodeAllocationCount: 0, + threadsPerAllocation: 0, + isPlaceholder: false, + hasStats: false, + }, +]; + +describe('ModelSelectLogic', () => { + const { mount } = new LogicMounter(ModelSelectLogic); + const { mount: mountCreateModelApiLogic } = new LogicMounter(CreateModelApiLogic); + const { mount: mountCachedFetchModelsApiLogic } = new LogicMounter(CachedFetchModelsApiLogic); + const { mount: mountStartModelApiLogic } = new LogicMounter(StartModelApiLogic); + + beforeEach(() => { + jest.clearAllMocks(); + mountCreateModelApiLogic(); + mountCachedFetchModelsApiLogic(); + mountStartModelApiLogic(); + mount(); + }); + + describe('listeners', () => { + describe('createModel', () => { + it('creates the model', () => { + const modelId = 'model_1'; + jest.spyOn(ModelSelectLogic.actions, 'createModelMakeRequest'); + + ModelSelectLogic.actions.createModel(modelId); + + expect(ModelSelectLogic.actions.createModelMakeRequest).toHaveBeenCalledWith({ modelId }); + }); + }); + + describe('createModelSuccess', () => { + it('starts polling models', () => { + jest.spyOn(ModelSelectLogic.actions, 'startPollingModels'); + + ModelSelectLogic.actions.createModelSuccess(CREATE_MODEL_API_RESPONSE); + + expect(ModelSelectLogic.actions.startPollingModels).toHaveBeenCalled(); + }); + }); + + describe('fetchModels', () => { + it('makes fetch models request', () => { + jest.spyOn(ModelSelectLogic.actions, 'fetchModelsMakeRequest'); + + ModelSelectLogic.actions.fetchModels(); + + expect(ModelSelectLogic.actions.fetchModelsMakeRequest).toHaveBeenCalled(); + }); + }); + + describe('startModel', () => { + it('makes start model request', () => { + const modelId = 'model_1'; + jest.spyOn(ModelSelectLogic.actions, 'startModelMakeRequest'); + + ModelSelectLogic.actions.startModel(modelId); + + expect(ModelSelectLogic.actions.startModelMakeRequest).toHaveBeenCalledWith({ modelId }); + }); + }); + + describe('startModelSuccess', () => { + it('starts polling models', () => { + jest.spyOn(ModelSelectLogic.actions, 'startPollingModels'); + + ModelSelectLogic.actions.startModelSuccess(CREATE_MODEL_API_RESPONSE); + + expect(ModelSelectLogic.actions.startPollingModels).toHaveBeenCalled(); + }); + }); + }); + + describe('selectors', () => { + describe('areActionButtonsDisabled', () => { + it('is set to false if create and start APIs are idle', () => { + CreateModelApiLogic.actions.apiReset(); + StartModelApiLogic.actions.apiReset(); + + expect(ModelSelectLogic.values.areActionButtonsDisabled).toBe(false); + }); + it('is set to true if create API is making a request', () => { + CreateModelApiLogic.actions.makeRequest({ modelId: 'model_1' }); + + expect(ModelSelectLogic.values.areActionButtonsDisabled).toBe(true); + }); + it('is set to true if start API is making a request', () => { + StartModelApiLogic.actions.makeRequest({ modelId: 'model_1' }); + + expect(ModelSelectLogic.values.areActionButtonsDisabled).toBe(true); + }); + }); + + describe('modelStateChangeError', () => { + it('gets error from API error response', () => { + const error = { + body: { + error: 'some-error', + message: 'some-error-message', + statusCode: 500, + }, + } as HttpError; + + StartModelApiLogic.actions.apiError(error); + + expect(ModelSelectLogic.values.modelStateChangeError).toEqual('some-error-message'); + }); + }); + + describe('selectableModels', () => { + it('gets models data from API response', () => { + CachedFetchModelsApiLogic.actions.apiSuccess(FETCH_MODELS_API_DATA_RESPONSE); + + expect(ModelSelectLogic.values.selectableModels).toEqual(FETCH_MODELS_API_DATA_RESPONSE); + }); + }); + + describe('isLoading', () => { + it('is set to true if the fetch API is loading the first time', () => { + CachedFetchModelsApiLogic.actions.apiReset(); + + expect(ModelSelectLogic.values.isLoading).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_logic.ts new file mode 100644 index 0000000000000..5cfa2148203e1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_logic.ts @@ -0,0 +1,139 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { HttpError, Status } from '../../../../../../../common/types/api'; +import { MlModel } from '../../../../../../../common/types/ml'; +import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors'; +import { + CachedFetchModelsApiLogic, + CachedFetchModlesApiLogicActions, +} from '../../../../api/ml_models/cached_fetch_models_api_logic'; +import { + CreateModelApiLogic, + CreateModelApiLogicActions, +} from '../../../../api/ml_models/create_model_api_logic'; +import { FetchModelsApiResponse } from '../../../../api/ml_models/fetch_models_api_logic'; +import { + StartModelApiLogic, + StartModelApiLogicActions, +} from '../../../../api/ml_models/start_model_api_logic'; + +export interface ModelSelectActions { + createModel: (modelId: string) => { modelId: string }; + createModelError: CreateModelApiLogicActions['apiError']; + createModelMakeRequest: CreateModelApiLogicActions['makeRequest']; + createModelSuccess: CreateModelApiLogicActions['apiSuccess']; + + fetchModels: () => void; + fetchModelsError: CachedFetchModlesApiLogicActions['apiError']; + fetchModelsMakeRequest: CachedFetchModlesApiLogicActions['makeRequest']; + fetchModelsSuccess: CachedFetchModlesApiLogicActions['apiSuccess']; + startPollingModels: CachedFetchModlesApiLogicActions['startPolling']; + + startModel: (modelId: string) => { modelId: string }; + startModelError: CreateModelApiLogicActions['apiError']; + startModelMakeRequest: StartModelApiLogicActions['makeRequest']; + startModelSuccess: StartModelApiLogicActions['apiSuccess']; +} + +export interface ModelSelectValues { + areActionButtonsDisabled: boolean; + createModelError: HttpError | undefined; + createModelStatus: Status; + isLoading: boolean; + isInitialLoading: boolean; + modelStateChangeError: string | undefined; + modelsData: FetchModelsApiResponse | undefined; + modelsStatus: Status; + selectableModels: MlModel[]; + startModelError: HttpError | undefined; + startModelStatus: Status; +} + +export const ModelSelectLogic = kea>({ + actions: { + createModel: (modelId: string) => ({ modelId }), + fetchModels: true, + startModel: (modelId: string) => ({ modelId }), + }, + connect: { + actions: [ + CreateModelApiLogic, + [ + 'makeRequest as createModelMakeRequest', + 'apiSuccess as createModelSuccess', + 'apiError as createModelError', + ], + CachedFetchModelsApiLogic, + [ + 'makeRequest as fetchModelsMakeRequest', + 'apiSuccess as fetchModelsSuccess', + 'apiError as fetchModelsError', + 'startPolling as startPollingModels', + ], + StartModelApiLogic, + [ + 'makeRequest as startModelMakeRequest', + 'apiSuccess as startModelSuccess', + 'apiError as startModelError', + ], + ], + values: [ + CreateModelApiLogic, + ['status as createModelStatus', 'error as createModelError'], + CachedFetchModelsApiLogic, + ['modelsData', 'status as modelsStatus', 'isInitialLoading'], + StartModelApiLogic, + ['status as startModelStatus', 'error as startModelError'], + ], + }, + events: ({ actions }) => ({ + afterMount: () => { + actions.startPollingModels(); + }, + }), + listeners: ({ actions }) => ({ + createModel: ({ modelId }) => { + actions.createModelMakeRequest({ modelId }); + }, + createModelSuccess: () => { + actions.startPollingModels(); + }, + fetchModels: () => { + actions.fetchModelsMakeRequest({}); + }, + startModel: ({ modelId }) => { + actions.startModelMakeRequest({ modelId }); + }, + startModelSuccess: () => { + actions.startPollingModels(); + }, + }), + path: ['enterprise_search', 'content', 'model_select_logic'], + selectors: ({ selectors }) => ({ + areActionButtonsDisabled: [ + () => [selectors.createModelStatus, selectors.startModelStatus], + (createModelStatus: Status, startModelStatus: Status) => + createModelStatus === Status.LOADING || startModelStatus === Status.LOADING, + ], + modelStateChangeError: [ + () => [selectors.createModelError, selectors.startModelError], + (createModelError?: HttpError, startModelError?: HttpError) => { + if (!createModelError && !startModelError) return undefined; + + return getErrorsFromHttpResponse(createModelError ?? startModelError!)[0]; + }, + ], + selectableModels: [ + () => [selectors.modelsData], + (response: FetchModelsApiResponse) => response ?? [], + ], + isLoading: [() => [selectors.isInitialLoading], (isInitialLoading) => isInitialLoading], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.test.tsx new file mode 100644 index 0000000000000..411bb8947257c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.test.tsx @@ -0,0 +1,103 @@ +/* + * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiBadge, EuiText } from '@elastic/eui'; + +import { MlModelDeploymentState } from '../../../../../../../common/types/ml'; +import { TrainedModelHealth } from '../ml_model_health'; + +import { + DeployModelButton, + getContextMenuPanel, + ModelSelectOption, + ModelSelectOptionProps, + StartModelButton, +} from './model_select_option'; + +const DEFAULT_PROPS: ModelSelectOptionProps = { + modelId: 'model_1', + type: 'ner', + label: 'Model 1', + title: 'Model 1', + description: 'Model 1 description', + license: 'elastic', + deploymentState: MlModelDeploymentState.NotDeployed, + startTime: 0, + targetAllocationCount: 0, + nodeAllocationCount: 0, + threadsPerAllocation: 0, + isPlaceholder: false, + hasStats: false, +}; + +describe('ModelSelectOption', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({}); + }); + it('renders with license badge if present', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiBadge)).toHaveLength(1); + }); + it('renders without license badge if not present', () => { + const props = { + ...DEFAULT_PROPS, + license: undefined, + }; + + const wrapper = shallow(); + expect(wrapper.find(EuiBadge)).toHaveLength(0); + }); + it('renders with description if present', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiText)).toHaveLength(1); + }); + it('renders without description if not present', () => { + const props = { + ...DEFAULT_PROPS, + description: undefined, + }; + + const wrapper = shallow(); + expect(wrapper.find(EuiText)).toHaveLength(0); + }); + it('renders deploy button for a model placeholder', () => { + const props = { + ...DEFAULT_PROPS, + isPlaceholder: true, + }; + + const wrapper = shallow(); + expect(wrapper.find(DeployModelButton)).toHaveLength(1); + }); + it('renders start button for a downloaded model', () => { + const props = { + ...DEFAULT_PROPS, + deploymentState: MlModelDeploymentState.Downloaded, + }; + + const wrapper = shallow(); + expect(wrapper.find(StartModelButton)).toHaveLength(1); + }); + it('renders status badge if there is no action button', () => { + const wrapper = shallow(); + expect(wrapper.find(TrainedModelHealth)).toHaveLength(1); + }); + + describe('getContextMenuPanel', () => { + it('gets model details link if URL is present', () => { + const panels = getContextMenuPanel('https://model.ai'); + expect(panels[0].items).toHaveLength(2); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx index a9efa40644540..3133dc6feb3bd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx @@ -5,55 +5,250 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiTitle } from '@elastic/eui'; +import { useActions, useValues } from 'kea'; import { - getMlModelTypesForModelConfig, - parseModelStateFromStats, - parseModelStateReasonFromStats, -} from '../../../../../../../common/ml_inference_pipeline'; -import { TrainedModel } from '../../../../api/ml_models/ml_trained_models_logic'; -import { getMLType, getModelDisplayTitle } from '../../../shared/ml_inference/utils'; + EuiBadge, + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiRadio, + EuiText, + EuiTextColor, + EuiTitle, + useIsWithinMaxBreakpoint, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { MlModel, MlModelDeploymentState } from '../../../../../../../common/types/ml'; +import { KibanaLogic } from '../../../../../shared/kibana'; import { TrainedModelHealth } from '../ml_model_health'; -import { MLModelTypeBadge } from '../ml_model_type_badge'; - -export interface MlModelSelectOptionProps { - model: TrainedModel; -} -export const MlModelSelectOption: React.FC = ({ model }) => { - const type = getMLType(getMlModelTypesForModelConfig(model)); - const title = getModelDisplayTitle(type); + +import { ModelSelectLogic } from './model_select_logic'; +import { TRAINED_MODELS_PATH } from './utils'; + +export const getContextMenuPanel = ( + modelDetailsPageUrl?: string +): EuiContextMenuPanelDescriptor[] => { + return [ + { + id: 0, + items: [ + { + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.modelSelectOption.actionMenu.tuneModelPerformance.label', + { + defaultMessage: 'Tune model performance', + } + ), + icon: 'controlsHorizontal', + onClick: () => + KibanaLogic.values.navigateToUrl(TRAINED_MODELS_PATH, { + shouldNotCreateHref: true, + }), + }, + ...(modelDetailsPageUrl + ? [ + { + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.modelSelectOption.actionMenu.modelDetails.label', + { + defaultMessage: 'Model details', + } + ), + icon: 'popout', + href: modelDetailsPageUrl, + target: '_blank', + }, + ] + : []), + ], + }, + ]; +}; + +export type ModelSelectOptionProps = MlModel & { + label: string; + checked?: 'on'; +}; + +export const DeployModelButton: React.FC<{ onClick: () => void; disabled: boolean }> = ({ + onClick, + disabled, +}) => { return ( - - - -

{title ?? model.model_id}

-
+ + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.modelSelectOption.deployButton.label', + { + defaultMessage: 'Deploy', + } + )} + + ); +}; + +export const StartModelButton: React.FC<{ onClick: () => void; disabled: boolean }> = ({ + onClick, + disabled, +}) => { + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.modelSelectOption.startButton.label', + { + defaultMessage: 'Start', + } + )} + + ); +}; + +export const ModelMenuPopover: React.FC<{ + onClick: () => void; + closePopover: () => void; + isOpen: boolean; + modelDetailsPageUrl?: string; +}> = ({ onClick, closePopover, isOpen, modelDetailsPageUrl }) => { + return ( + + } + isOpen={isOpen} + closePopover={closePopover} + anchorPosition="leftCenter" + panelPaddingSize="none" + > + + + ); +}; + +export const ModelSelectOption: React.FC = ({ + modelId, + title, + description, + license, + deploymentState, + deploymentStateReason, + modelDetailsPageUrl, + isPlaceholder, + checked, +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const onMenuButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen); + const closePopover = () => setIsPopoverOpen(false); + + const { createModel, startModel } = useActions(ModelSelectLogic); + const { areActionButtonsDisabled } = useValues(ModelSelectLogic); + + return ( + + {/* Selection radio button */} + + null} + // @ts-ignore + inert + /> - - - {title && ( + {/* Title, model ID, description, license */} + + + + +

{title}

+
+
+ + {modelId} + + {(license || description) && ( - {model.model_id} + + {license && ( + + {/* Wrap in a div to prevent the badge from growing to a whole row on mobile */} +
+ + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.modelSelectOption.licenseBadge.label', + { + defaultMessage: 'License: {license}', + values: { + license, + }, + } + )} + +
+
+ )} + {description && ( + + +
+ {description} +
+
+
+ )} +
)} - +
+
+ {/* Status indicator OR action button */} + + {/* Wrap in a div to prevent the badge/button from growing to a whole row on mobile */} +
+ {isPlaceholder ? ( + createModel(modelId)} + disabled={areActionButtonsDisabled} + /> + ) : deploymentState === MlModelDeploymentState.Downloaded ? ( + startModel(modelId)} + disabled={areActionButtonsDisabled} + /> + ) : ( - - - - - - - - - + )} +
+
+ {/* Actions menu */} + +
); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/text_expansion_callout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/text_expansion_callout_logic.ts index 06d4f553bbabd..35544bc5d5685 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/text_expansion_callout_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/text_expansion_callout_logic.ts @@ -179,7 +179,7 @@ export const TextExpansionCalloutLogic = kea< afterMount: async () => { const elserModel = await KibanaLogic.values.ml.elasticModels?.getELSER({ version: 2 }); if (elserModel != null) { - actions.setElserModelId(elserModel.name); + actions.setElserModelId(elserModel.model_id); actions.fetchTextExpansionModel(); } }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx index 47136ff90f799..65bfbc0951d30 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx @@ -13,6 +13,7 @@ import { shallow } from 'enzyme'; import { EuiHealth } from '@elastic/eui'; +import { MlModelDeploymentState } from '../../../../../../common/types/ml'; import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; import { TrainedModelHealth } from './ml_model_health'; @@ -30,6 +31,18 @@ describe('TrainedModelHealth', () => { pipelineReferences: [], types: ['pytorch'], }; + it('renders model downloading', () => { + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Downloading'); + expect(health.prop('color')).toEqual('warning'); + }); + it('renders model downloaded', () => { + const wrapper = shallow(); + const health = wrapper.find(EuiHealth); + expect(health.prop('children')).toEqual('Downloaded'); + expect(health.prop('color')).toEqual('subdued'); + }); it('renders model started', () => { const pipeline: InferencePipeline = { ...commonModelData, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx index 45fd54b6bf4fd..133582520deb8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx @@ -12,8 +12,33 @@ import { EuiHealth, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { MlModelDeploymentState } from '../../../../../../common/types/ml'; import { TrainedModelState } from '../../../../../../common/types/pipelines'; +const modelDownloadingText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.downloading', + { + defaultMessage: 'Downloading', + } +); +const modelDownloadingTooltip = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.downloading.tooltip', + { + defaultMessage: 'This trained model is downloading', + } +); +const modelDownloadedText = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.downloaded', + { + defaultMessage: 'Downloaded', + } +); +const modelDownloadedTooltip = i18n.translate( + 'xpack.enterpriseSearch.inferencePipelineCard.modelState.downloaded.tooltip', + { + defaultMessage: 'This trained model is downloaded and can be started', + } +); const modelStartedText = i18n.translate( 'xpack.enterpriseSearch.inferencePipelineCard.modelState.started', { @@ -73,7 +98,7 @@ const modelNotDeployedTooltip = i18n.translate( ); export interface TrainedModelHealthProps { - modelState: TrainedModelState; + modelState: TrainedModelState | MlModelDeploymentState; modelStateReason?: string; } @@ -87,27 +112,52 @@ export const TrainedModelHealth: React.FC = ({ tooltipText: React.ReactNode; }; switch (modelState) { - case TrainedModelState.Started: + case TrainedModelState.NotDeployed: + case MlModelDeploymentState.NotDeployed: modelHealth = { - healthColor: 'success', - healthText: modelStartedText, - tooltipText: modelStartedTooltip, + healthColor: 'danger', + healthText: modelNotDeployedText, + tooltipText: modelNotDeployedTooltip, }; break; - case TrainedModelState.Stopping: + case MlModelDeploymentState.Downloading: modelHealth = { healthColor: 'warning', - healthText: modelStoppingText, - tooltipText: modelStoppingTooltip, + healthText: modelDownloadingText, + tooltipText: modelDownloadingTooltip, + }; + break; + case MlModelDeploymentState.Downloaded: + modelHealth = { + healthColor: 'subdued', + healthText: modelDownloadedText, + tooltipText: modelDownloadedTooltip, }; break; case TrainedModelState.Starting: + case MlModelDeploymentState.Starting: modelHealth = { healthColor: 'warning', healthText: modelStartingText, tooltipText: modelStartingTooltip, }; break; + case TrainedModelState.Started: + case MlModelDeploymentState.Started: + case MlModelDeploymentState.FullyAllocated: + modelHealth = { + healthColor: 'success', + healthText: modelStartedText, + tooltipText: modelStartedTooltip, + }; + break; + case TrainedModelState.Stopping: + modelHealth = { + healthColor: 'warning', + healthText: modelStoppingText, + tooltipText: modelStoppingTooltip, + }; + break; case TrainedModelState.Failed: modelHealth = { healthColor: 'danger', @@ -133,7 +183,7 @@ export const TrainedModelHealth: React.FC = ({ ), }; break; - case TrainedModelState.NotDeployed: + default: modelHealth = { healthColor: 'danger', healthText: modelNotDeployedText, diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts index 629be15a0bf3d..790b34a964d5b 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts @@ -10,16 +10,27 @@ import { MlTrainedModels } from '@kbn/ml-plugin/server'; import { MlModelDeploymentState } from '../../../common/types/ml'; import { fetchMlModels } from './fetch_ml_models'; -import { E5_MODEL_ID, ELSER_MODEL_ID } from './utils'; +import { + E5_LINUX_OPTIMIZED_MODEL_ID, + E5_MODEL_ID, + ELSER_LINUX_OPTIMIZED_MODEL_ID, + ELSER_MODEL_ID, +} from './utils'; describe('fetchMlModels', () => { const mockTrainedModelsProvider = { getTrainedModels: jest.fn(), getTrainedModelsStats: jest.fn(), + getCuratedModelConfig: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); + // getCuratedModelConfig() default behavior is to return the cross-platform models + mockTrainedModelsProvider.getCuratedModelConfig.mockImplementation((modelName) => ({ + model_id: modelName === 'elser' ? ELSER_MODEL_ID : E5_MODEL_ID, + modelName, + })); }); it('errors when there is no trained model provider', () => { @@ -140,6 +151,111 @@ describe('fetchMlModels', () => { expect(models[1].modelId).toEqual(E5_MODEL_ID); // Placeholder }); + it('filters incompatible model variants of promoted models', async () => { + const mockModelConfigs = { + count: 2, + trained_model_configs: [ + { + model_id: E5_MODEL_ID, + inference_config: { + text_embedding: {}, + }, + }, + { + model_id: E5_LINUX_OPTIMIZED_MODEL_ID, + inference_config: { + text_embedding: {}, + }, + }, + { + model_id: ELSER_MODEL_ID, + inference_config: { + text_expansion: {}, + }, + }, + { + model_id: ELSER_LINUX_OPTIMIZED_MODEL_ID, + inference_config: { + text_expansion: {}, + }, + }, + ], + }; + const mockModelStats = { + trained_model_stats: mockModelConfigs.trained_model_configs.map((modelConfig) => ({ + model_id: modelConfig.model_id, + })), + }; + + mockTrainedModelsProvider.getTrainedModels.mockImplementation(() => + Promise.resolve(mockModelConfigs) + ); + mockTrainedModelsProvider.getTrainedModelsStats.mockImplementation(() => + Promise.resolve(mockModelStats) + ); + + const models = await fetchMlModels(mockTrainedModelsProvider as unknown as MlTrainedModels); + + expect(models.length).toBe(2); + expect(models[0].modelId).toEqual(ELSER_MODEL_ID); + expect(models[1].modelId).toEqual(E5_MODEL_ID); + }); + + it('filters incompatible model variants of promoted models (Linux variants)', async () => { + const mockModelConfigs = { + count: 2, + trained_model_configs: [ + { + model_id: E5_MODEL_ID, + inference_config: { + text_embedding: {}, + }, + }, + { + model_id: E5_LINUX_OPTIMIZED_MODEL_ID, + inference_config: { + text_embedding: {}, + }, + }, + { + model_id: ELSER_MODEL_ID, + inference_config: { + text_expansion: {}, + }, + }, + { + model_id: ELSER_LINUX_OPTIMIZED_MODEL_ID, + inference_config: { + text_expansion: {}, + }, + }, + ], + }; + const mockModelStats = { + trained_model_stats: mockModelConfigs.trained_model_configs.map((modelConfig) => ({ + model_id: modelConfig.model_id, + })), + }; + + mockTrainedModelsProvider.getTrainedModels.mockImplementation(() => + Promise.resolve(mockModelConfigs) + ); + mockTrainedModelsProvider.getTrainedModelsStats.mockImplementation(() => + Promise.resolve(mockModelStats) + ); + mockTrainedModelsProvider.getCuratedModelConfig.mockImplementation((modelName) => ({ + model_id: + modelName === 'elser' ? ELSER_LINUX_OPTIMIZED_MODEL_ID : E5_LINUX_OPTIMIZED_MODEL_ID, + modelName, + })); + + const models = await fetchMlModels(mockTrainedModelsProvider as unknown as MlTrainedModels); + + expect(models.length).toBe(2); + expect(models[0].modelId).toEqual(ELSER_LINUX_OPTIMIZED_MODEL_ID); + expect(models[1].modelId).toEqual(E5_LINUX_OPTIMIZED_MODEL_ID); + }); + it('sets deployment state on models', async () => { const mockModelConfigs = { count: 3, diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.ts b/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.ts index 616bcf7277676..1062356a5dbeb 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.ts @@ -12,14 +12,21 @@ import { MlModelDeploymentState, MlModel } from '../../../common/types/ml'; import { BASE_MODEL, + ELSER_LINUX_OPTIMIZED_MODEL_PLACEHOLDER, ELSER_MODEL_ID, ELSER_MODEL_PLACEHOLDER, + E5_LINUX_OPTIMIZED_MODEL_PLACEHOLDER, E5_MODEL_ID, E5_MODEL_PLACEHOLDER, LANG_IDENT_MODEL_ID, MODEL_TITLES_BY_TYPE, + E5_LINUX_OPTIMIZED_MODEL_ID, + ELSER_LINUX_OPTIMIZED_MODEL_ID, } from './utils'; +let compatibleElserModelId = ELSER_MODEL_ID; +let compatibleE5ModelId = E5_MODEL_ID; + /** * Fetches and enriches trained model information and deployment status. Pins promoted models (ELSER, E5) to the top. If a promoted model doesn't exist, a placeholder will be used. * @@ -33,8 +40,18 @@ export const fetchMlModels = async ( throw new Error('Machine Learning is not enabled'); } - // This array will contain all models, let's add placeholders first - const models: MlModel[] = [ELSER_MODEL_PLACEHOLDER, E5_MODEL_PLACEHOLDER]; + // Set the compatible ELSER and E5 model IDs based on platform architecture + [compatibleElserModelId, compatibleE5ModelId] = await fetchCompatiblePromotedModelIds( + trainedModelsProvider + ); + + // This array will contain all models, let's add placeholders first (compatible variants only) + const models: MlModel[] = [ + ELSER_MODEL_PLACEHOLDER, + ELSER_LINUX_OPTIMIZED_MODEL_PLACEHOLDER, + E5_MODEL_PLACEHOLDER, + E5_LINUX_OPTIMIZED_MODEL_PLACEHOLDER, + ].filter((model) => isCompatiblePromotedModelId(model.modelId)); // Fetch all models and their deployment stats using the ML client const modelsResponse = await trainedModelsProvider.getTrainedModels({}); @@ -69,6 +86,27 @@ export const fetchMlModels = async ( return models.sort(sortModels); }; +/** + * Fetches model IDs of promoted models (ELSER, E5) that are compatible with the platform architecture. The fetches + * are executed in parallel. + * Defaults to the cross-platform variant of a model if its ID is not present in the trained models client's response. + * @param trainedModelsProvider Trained ML models provider + * @returns Array of model IDs [0: ELSER, 1: E5] + */ +export const fetchCompatiblePromotedModelIds = async (trainedModelsProvider: MlTrainedModels) => { + const compatibleModelConfigs = await Promise.all([ + trainedModelsProvider.getCuratedModelConfig('elser', { version: 2 }), + trainedModelsProvider.getCuratedModelConfig('e5'), + ]); + + return [ + compatibleModelConfigs.find((modelConfig) => modelConfig?.modelName === 'elser')?.model_id ?? + ELSER_MODEL_ID, + compatibleModelConfigs.find((modelConfig) => modelConfig?.modelName === 'e5')?.model_id ?? + E5_MODEL_ID, + ]; +}; + const getModel = (modelConfig: MlTrainedModelConfig, modelStats?: MlTrainedModelStats): MlModel => { { const modelId = modelConfig.model_id; @@ -78,7 +116,12 @@ const getModel = (modelConfig: MlTrainedModelConfig, modelStats?: MlTrainedModel modelId, type, title: getUserFriendlyTitle(modelId, type), - isPromoted: [ELSER_MODEL_ID, E5_MODEL_ID].includes(modelId), + isPromoted: [ + ELSER_MODEL_ID, + ELSER_LINUX_OPTIMIZED_MODEL_ID, + E5_MODEL_ID, + E5_LINUX_OPTIMIZED_MODEL_ID, + ].includes(modelId), }; // Enrich deployment stats @@ -127,7 +170,21 @@ const mergeModel = (model: MlModel, models: MlModel[]) => { } }; +const isCompatiblePromotedModelId = (modelId: string) => + [compatibleElserModelId, compatibleE5ModelId].includes(modelId); + +/** + * A model is supported if: + * - The inference type is supported, AND + * - The model is the compatible variant of ELSER/E5, or it's a 3rd party model + */ const isSupportedModel = (modelConfig: MlTrainedModelConfig) => + isSupportedInferenceType(modelConfig) && + ((!modelConfig.model_id.startsWith(ELSER_MODEL_ID) && + !modelConfig.model_id.startsWith(E5_MODEL_ID)) || + isCompatiblePromotedModelId(modelConfig.model_id)); + +const isSupportedInferenceType = (modelConfig: MlTrainedModelConfig) => Object.keys(modelConfig.inference_config || {}).some((inferenceType) => Object.keys(MODEL_TITLES_BY_TYPE).includes(inferenceType) ) || modelConfig.model_id === LANG_IDENT_MODEL_ID; @@ -136,13 +193,13 @@ const isSupportedModel = (modelConfig: MlTrainedModelConfig) => * Sort function for models; makes ELSER go to the top, then E5, then the rest of the models sorted by title. */ const sortModels = (m1: MlModel, m2: MlModel) => - m1.modelId === ELSER_MODEL_ID + m1.modelId.startsWith(ELSER_MODEL_ID) ? -1 - : m2.modelId === ELSER_MODEL_ID + : m2.modelId.startsWith(ELSER_MODEL_ID) ? 1 - : m1.modelId === E5_MODEL_ID + : m1.modelId.startsWith(E5_MODEL_ID) ? -1 - : m2.modelId === E5_MODEL_ID + : m2.modelId.startsWith(E5_MODEL_ID) ? 1 : m1.title.localeCompare(m2.title); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts index 4f65dbf9ced64..becd34a6c3c95 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts @@ -43,7 +43,7 @@ export const startMlModelDeployment = async ( // we're downloaded already, but not deployed yet - let's deploy it const startRequest: MlStartTrainedModelDeploymentRequest = { model_id: modelName, - wait_for: 'started', + wait_for: 'starting', }; await trainedModelsProvider.startTrainedModelDeployment(startRequest); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts b/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts index 29aded727280d..817e8716f73e3 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts @@ -11,7 +11,9 @@ import { SUPPORTED_PYTORCH_TASKS } from '@kbn/ml-trained-models-utils'; import { MlModelDeploymentState, MlModel } from '../../../common/types/ml'; export const ELSER_MODEL_ID = '.elser_model_2'; +export const ELSER_LINUX_OPTIMIZED_MODEL_ID = '.elser_model_2_linux-x86_64'; export const E5_MODEL_ID = '.multilingual-e5-small'; +export const E5_LINUX_OPTIMIZED_MODEL_ID = '.multilingual-e5-small_linux-x86_64'; export const LANG_IDENT_MODEL_ID = 'lang_ident_model_1'; export const MODEL_TITLES_BY_TYPE: Record = { @@ -64,24 +66,36 @@ export const ELSER_MODEL_PLACEHOLDER: MlModel = { ...BASE_MODEL, modelId: ELSER_MODEL_ID, type: SUPPORTED_PYTORCH_TASKS.TEXT_EXPANSION, - title: 'Elastic Learned Sparse EncodeR (ELSER)', + title: 'ELSER (Elastic Learned Sparse EncodeR)', description: i18n.translate('xpack.enterpriseSearch.modelCard.elserPlaceholder.description', { defaultMessage: - 'ELSER is designed to efficiently use context in natural language queries with better results than BM25 alone.', + "ELSER is Elastic's NLP model for English semantic search, utilizing sparse vectors. It prioritizes intent and contextual meaning over literal term matching, optimized specifically for English documents and queries on the Elastic platform.", }), - license: 'Elastic', isPlaceholder: true, }; +export const ELSER_LINUX_OPTIMIZED_MODEL_PLACEHOLDER = { + ...ELSER_MODEL_PLACEHOLDER, + modelId: ELSER_LINUX_OPTIMIZED_MODEL_ID, + title: 'ELSER (Elastic Learned Sparse EncodeR), optimized for linux-x86_64', +}; + export const E5_MODEL_PLACEHOLDER: MlModel = { ...BASE_MODEL, modelId: E5_MODEL_ID, type: SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING, - title: 'E5 Multilingual Embedding', + title: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', description: i18n.translate('xpack.enterpriseSearch.modelCard.e5Placeholder.description', { - defaultMessage: 'Multilingual dense vector embedding generator.', + defaultMessage: + 'E5 is an NLP model that enables you to perform multi-lingual semantic search by using dense vector representations. This model performs best for non-English language documents and queries.', }), license: 'MIT', modelDetailsPageUrl: 'https://huggingface.co/intfloat/multilingual-e5-small', isPlaceholder: true, }; + +export const E5_LINUX_OPTIMIZED_MODEL_PLACEHOLDER = { + ...E5_MODEL_PLACEHOLDER, + modelId: E5_LINUX_OPTIMIZED_MODEL_ID, + title: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', +}; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index b6950ba672817..931168f545b55 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -671,7 +671,9 @@ describe('agent policy', () => { jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); mockedAppContextService.getUninstallTokenService.mockReturnValueOnce({ - checkTokenValidityForPolicy: jest.fn().mockRejectedValueOnce(new Error('reason')), + checkTokenValidityForPolicy: jest + .fn() + .mockResolvedValueOnce({ error: new Error('reason') }), } as unknown as UninstallTokenServiceInterface); const soClient = getAgentPolicyCreateMock(); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 5e8c897d5611a..568829fda978e 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -1220,10 +1220,14 @@ class AgentPolicyService { if (agentPolicy?.is_protected) { const uninstallTokenService = appContextService.getUninstallTokenService(); - try { - await uninstallTokenService?.checkTokenValidityForPolicy(policyId); - } catch (e) { - throw new Error(`Cannot enable Agent Tamper Protection: ${e.message}`); + const uninstallTokenError = await uninstallTokenService?.checkTokenValidityForPolicy( + policyId + ); + + if (uninstallTokenError) { + throw new Error( + `Cannot enable Agent Tamper Protection: ${uninstallTokenError.error.message}` + ); } } } diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts index 77d2a2a272caf..b3d08e9a0b8e9 100644 --- a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts +++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { backOff } from 'exponential-backoff'; - import { generateKeyPairSync, createSign, randomBytes } from 'crypto'; +import { backOff } from 'exponential-backoff'; + import type { LoggerFactory, Logger } from '@kbn/core/server'; import type { KibanaRequest } from '@kbn/core-http-server'; import type { @@ -202,10 +202,10 @@ export class MessageSigningService implements MessageSigningServiceInterface { soDoc = await this.getCurrentKeyPairObj(); }, { - maxDelay: 60 * 60 * 1000, // 1 hour in milliseconds startingDelay: 1000, // 1 second + maxDelay: 3000, // 3 seconds jitter: 'full', - numOfAttempts: Infinity, + numOfAttempts: 10, retry: (_err: Error, attempt: number) => { // not logging the error since we don't control what's in the error and it might contain sensitive data // ESO already logs specific caught errors before passing the error along @@ -250,7 +250,13 @@ export class MessageSigningService implements MessageSigningServiceInterface { } | undefined > { - const currentKeyPair = await this.getCurrentKeyPairObjWithRetry(); + let currentKeyPair; + try { + currentKeyPair = await this.getCurrentKeyPairObjWithRetry(); + } catch (e) { + throw new MessageSigningError('Cannot read existing Message Signing Key pair'); + } + if (!currentKeyPair) { return; } diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts index 4b3be1de81632..a782fc605ccf5 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts @@ -13,6 +13,8 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { UninstallTokenError } from '../../../../common/errors'; + import { SO_SEARCH_LIMIT } from '../../../../common'; import type { @@ -252,7 +254,7 @@ describe('UninstallTokenService', () => { mockCreatePointInTimeFinder(canEncrypt, defaultBuckets); await expect(uninstallTokenService.getTokenMetadata()).rejects.toThrowError( - 'Uninstall Token is missing creation date.' + 'Invalid uninstall token: Saved object is missing creation date.' ); }); @@ -264,7 +266,7 @@ describe('UninstallTokenService', () => { mockCreatePointInTimeFinder(canEncrypt, defaultBuckets); await expect(uninstallTokenService.getTokenMetadata()).rejects.toThrowError( - 'Uninstall Token is missing policy ID.' + 'Invalid uninstall token: Saved object is missing the policy id attribute.' ); }); }); @@ -517,60 +519,94 @@ describe('UninstallTokenService', () => { }; describe('checkTokenValidityForAllPolicies', () => { - it('resolves if all of the tokens are available', async () => { + it('returns null if all of the tokens are available', async () => { mockCreatePointInTimeFinderAsInternalUser(); await expect( uninstallTokenService.checkTokenValidityForAllPolicies() - ).resolves.not.toThrowError(); + ).resolves.toBeNull(); }); - it('rejects if any of the tokens is missing', async () => { + it('returns error if any of the tokens is missing', async () => { mockCreatePointInTimeFinderAsInternalUser([okaySO, missingTokenSO2]); await expect( uninstallTokenService.checkTokenValidityForAllPolicies() - ).rejects.toThrowError( - 'Invalid uninstall token: Saved object is missing the `token` attribute.' - ); + ).resolves.toStrictEqual({ + error: new UninstallTokenError( + 'Invalid uninstall token: Saved object is missing the token attribute.' + ), + }); }); - it('rejects if token decryption gives error', async () => { + it('returns error if token decryption gives error', async () => { mockCreatePointInTimeFinderAsInternalUser([okaySO, errorWithDecryptionSO2]); await expect( uninstallTokenService.checkTokenValidityForAllPolicies() - ).rejects.toThrowError('Error when reading Uninstall Token: error reason'); + ).resolves.toStrictEqual({ + error: new UninstallTokenError( + "Error when reading Uninstall Token with id 'test-so-id-two'." + ), + }); + }); + + it('throws error in case of unknown error', async () => { + esoClientMock.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockRejectedValueOnce('some error'); + + await expect( + uninstallTokenService.checkTokenValidityForAllPolicies() + ).rejects.toThrowError('Unknown error happened while checking Uninstall Tokens validity'); }); }); describe('checkTokenValidityForPolicy', () => { - it('resolves if token is available', async () => { + it('returns empty array if token is available', async () => { mockCreatePointInTimeFinderAsInternalUser(); await expect( uninstallTokenService.checkTokenValidityForPolicy(okaySO.attributes.policy_id) - ).resolves.not.toThrowError(); + ).resolves.toBeNull(); }); - it('rejects if token is missing', async () => { + it('returns error if token is missing', async () => { mockCreatePointInTimeFinderAsInternalUser([okaySO, missingTokenSO2]); await expect( uninstallTokenService.checkTokenValidityForPolicy(missingTokenSO2.attributes.policy_id) - ).rejects.toThrowError( - 'Invalid uninstall token: Saved object is missing the `token` attribute.' - ); + ).resolves.toStrictEqual({ + error: new UninstallTokenError( + 'Invalid uninstall token: Saved object is missing the token attribute.' + ), + }); }); - it('rejects if token decryption gives error', async () => { + it('returns error if token decryption gives error', async () => { mockCreatePointInTimeFinderAsInternalUser([okaySO, errorWithDecryptionSO2]); await expect( uninstallTokenService.checkTokenValidityForPolicy( errorWithDecryptionSO2.attributes.policy_id ) - ).rejects.toThrowError('Error when reading Uninstall Token: error reason'); + ).resolves.toStrictEqual({ + error: new UninstallTokenError( + "Error when reading Uninstall Token with id 'test-so-id-two'." + ), + }); + }); + + it('throws error in case of unknown error', async () => { + esoClientMock.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockRejectedValueOnce('some error'); + + await expect( + uninstallTokenService.checkTokenValidityForPolicy( + errorWithDecryptionSO2.attributes.policy_id + ) + ).rejects.toThrowError('Unknown error happened while checking Uninstall Tokens validity'); }); }); }); diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 9e03e7869c584..7a2bdedffcdc8 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -55,6 +55,10 @@ interface UninstallTokenSOAggregation { by_policy_id: AggregationsMultiBucketAggregateBase; } +export interface UninstallTokenInvalidError { + error: UninstallTokenError; +} + export interface UninstallTokenServiceInterface { /** * Get uninstall token based on its id. @@ -140,14 +144,14 @@ export interface UninstallTokenServiceInterface { * * @param policyId policy Id to check */ - checkTokenValidityForPolicy(policyId: string): Promise; + checkTokenValidityForPolicy(policyId: string): Promise; /** * Check whether all policies have a valid uninstall token. Rejects returning promise if not. * * @param policyId policy Id to check */ - checkTokenValidityForAllPolicies(): Promise; + checkTokenValidityForAllPolicies(): Promise; } export class UninstallTokenService implements UninstallTokenServiceInterface { @@ -226,7 +230,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { const uninstallTokens: UninstallToken[] = tokenObject.map( ({ id: _id, attributes, created_at: createdAt, error }) => { if (error) { - throw new UninstallTokenError(`Error when reading Uninstall Token: ${error.message}`); + throw new UninstallTokenError(`Error when reading Uninstall Token with id '${_id}'.`); } this.assertPolicyId(attributes); @@ -489,13 +493,34 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { return this._soClient; } - public async checkTokenValidityForPolicy(policyId: string): Promise { - await this.getDecryptedTokensForPolicyIds([policyId]); + public async checkTokenValidityForPolicy( + policyId: string + ): Promise { + return await this.checkTokenValidity([policyId]); } - public async checkTokenValidityForAllPolicies(): Promise { + public async checkTokenValidityForAllPolicies(): Promise { const policyIds = await this.getAllPolicyIds(); - await this.getDecryptedTokensForPolicyIds(policyIds); + return await this.checkTokenValidity(policyIds); + } + + private async checkTokenValidity( + policyIds: string[] + ): Promise { + try { + await this.getDecryptedTokensForPolicyIds(policyIds); + } catch (error) { + if (error instanceof UninstallTokenError) { + // known errors are considered non-fatal + return { error }; + } else { + const errorMessage = 'Unknown error happened while checking Uninstall Tokens validity'; + appContextService.getLogger().error(`${errorMessage}: '${error}'`); + throw new UninstallTokenError(errorMessage); + } + } + + return null; } private get isEncryptionAvailable(): boolean { @@ -504,21 +529,25 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { private assertCreatedAt(createdAt: string | undefined): asserts createdAt is string { if (!createdAt) { - throw new UninstallTokenError('Uninstall Token is missing creation date.'); + throw new UninstallTokenError( + 'Invalid uninstall token: Saved object is missing creation date.' + ); } } private assertToken(attributes: UninstallTokenSOAttributes | undefined) { if (!attributes?.token && !attributes?.token_plain) { throw new UninstallTokenError( - 'Invalid uninstall token: Saved object is missing the `token` attribute.' + 'Invalid uninstall token: Saved object is missing the token attribute.' ); } } private assertPolicyId(attributes: UninstallTokenSOAttributes | undefined) { if (!attributes?.policy_id) { - throw new UninstallTokenError('Uninstall Token is missing policy ID.'); + throw new UninstallTokenError( + 'Invalid uninstall token: Saved object is missing the policy id attribute.' + ); } } } diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 60ce6460d0ac2..e0b3bdfea0bd3 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -14,7 +14,7 @@ import pMap from 'p-map'; import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; -import type { UninstallTokenError } from '../../common/errors'; +import { MessageSigningError } from '../../common/errors'; import { AUTO_UPDATE_PACKAGES } from '../../common/constants'; import type { PreconfigurationError } from '../../common/constants'; @@ -53,6 +53,7 @@ import { getPreconfiguredFleetServerHostFromConfig, } from './preconfiguration/fleet_server_host'; import { cleanUpOldFileIndices } from './setup/clean_old_fleet_indices'; +import type { UninstallTokenInvalidError } from './security/uninstall_token_service'; export interface SetupStatus { isInitialized: boolean; @@ -60,7 +61,8 @@ export interface SetupStatus { | PreconfigurationError | DefaultPackagesInstallationError | UpgradeManagedPackagePoliciesResult - | { error: UninstallTokenError } + | UninstallTokenInvalidError + | { error: MessageSigningError } >; } @@ -174,8 +176,6 @@ async function createSetupSideEffects( ).filter((result) => (result.errors ?? []).length > 0); stepSpan?.end(); - const nonFatalErrors = [...preconfiguredPackagesNonFatalErrors, ...packagePolicyUpgradeErrors]; - logger.debug('Upgrade Fleet package install versions'); stepSpan = apm.startSpan('Upgrade package install format version', 'preconfiguration'); await upgradePackageInstallVersion({ soClient, esClient, logger }); @@ -188,7 +188,16 @@ async function createSetupSideEffects( 'xpack.encryptedSavedObjects.encryptionKey is not configured, private key passphrase is being stored in plain text' ); } - await appContextService.getMessageSigningService()?.generateKeyPair(); + let messageSigningServiceNonFatalError: { error: MessageSigningError } | undefined; + try { + await appContextService.getMessageSigningService()?.generateKeyPair(); + } catch (error) { + if (error instanceof MessageSigningError) { + messageSigningServiceNonFatalError = { error }; + } else { + throw error; + } + } logger.debug('Generating Agent uninstall tokens'); if (!appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt) { @@ -204,11 +213,9 @@ async function createSetupSideEffects( } logger.debug('Checking validity of Uninstall Tokens'); - try { - await appContextService.getUninstallTokenService()?.checkTokenValidityForAllPolicies(); - } catch (error) { - nonFatalErrors.push({ error }); - } + const uninstallTokenError = await appContextService + .getUninstallTokenService() + ?.checkTokenValidityForAllPolicies(); stepSpan?.end(); stepSpan = apm.startSpan('Upgrade agent policy schema', 'preconfiguration'); @@ -222,6 +229,13 @@ async function createSetupSideEffects( await ensureDefaultEnrollmentAPIKeysExists(soClient, esClient); stepSpan?.end(); + const nonFatalErrors = [ + ...preconfiguredPackagesNonFatalErrors, + ...packagePolicyUpgradeErrors, + ...(messageSigningServiceNonFatalError ? [messageSigningServiceNonFatalError] : []), + ...(uninstallTokenError ? [uninstallTokenError] : []), + ]; + if (nonFatalErrors.length > 0) { logger.info('Encountered non fatal errors during Fleet setup'); formatNonFatalErrors(nonFatalErrors) diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx index 15b30ab3b79cf..5d6f6e6a7ce80 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx @@ -18,7 +18,7 @@ import moment from 'moment'; import { useTheme } from '@emotion/react'; import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { getPaddedAlertTimeRange } from '@kbn/observability-alert-details'; +import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import { get, identity } from 'lodash'; import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; import { useLogView } from '@kbn/logs-shared-plugin/public'; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx index d73aec96da4d1..17aadc136b2bb 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx @@ -24,6 +24,9 @@ const mockedChartStartContract = chartPluginMock.createStartContract(); jest.mock('@kbn/observability-alert-details', () => ({ AlertAnnotation: () => {}, AlertActiveTimeRangeAnnotation: () => {}, +})); + +jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ getPaddedAlertTimeRange: () => ({ from: '2023-03-28T10:43:13.802Z', to: '2023-03-29T13:14:09.581Z', diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx index 466d032b5c01f..6b19b7b340d5c 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx @@ -22,11 +22,8 @@ import { import { AlertSummaryField, TopAlert } from '@kbn/observability-plugin/public'; import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils'; import { Rule } from '@kbn/alerting-plugin/common'; -import { - AlertAnnotation, - getPaddedAlertTimeRange, - AlertActiveTimeRangeAnnotation, -} from '@kbn/observability-alert-details'; +import { AlertAnnotation, AlertActiveTimeRangeAnnotation } from '@kbn/observability-alert-details'; +import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import { metricValueFormatter } from '../../../../common/alerting/metrics/metric_value_formatter'; import { TIME_LABELS } from '../../common/criterion_preview_chart/criterion_preview_chart'; import { Threshold } from '../../common/components/threshold'; diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index da026aa7d7e93..cbcbdbbbc365f 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -78,7 +78,8 @@ "@kbn/custom-icons", "@kbn/profiling-utils", "@kbn/profiling-data-access-plugin", - "@kbn/core-http-request-handler-context-server" + "@kbn/core-http-request-handler-context-server", + "@kbn/observability-get-padded-alert-time-range-util" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.tsx index fbcfaf3c36b9f..af00ba7cb5c69 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.tsx @@ -66,8 +66,9 @@ export const staticValueOperation: OperationDefinition< }, getErrorMessage(layer, columnId) { const column = layer.columns[columnId] as StaticValueIndexPatternColumn; + const isValid = isValidNumber(column.params.value, false, undefined, undefined, 15); - return column.params.value != null && !isValidNumber(column.params.value) + return column.params.value != null && !isValid ? [ i18n.translate('xpack.lens.indexPattern.staticValueError', { defaultMessage: 'The static value of {value} is not a valid number', @@ -89,7 +90,8 @@ export const staticValueOperation: OperationDefinition< const params = currentColumn.params; // TODO: improve this logic const useDisplayLabel = currentColumn.label !== defaultLabel; - const label = isValidNumber(params.value) + const isValid = isValidNumber(params.value, false, undefined, undefined, 15); + const label = isValid ? useDisplayLabel ? currentColumn.label : params?.value ?? defaultLabel @@ -98,11 +100,11 @@ export const staticValueOperation: OperationDefinition< return [ { type: 'function', - function: isValidNumber(params.value) ? 'mathColumn' : 'mapColumn', + function: isValid ? 'mathColumn' : 'mapColumn', arguments: { id: [columnId], name: [label || defaultLabel], - expression: [String(isValidNumber(params.value) ? params.value! : defaultValue)], + expression: [String(isValid ? params.value! : defaultValue)], }, }, ]; @@ -163,7 +165,10 @@ export const staticValueOperation: OperationDefinition< const onChange = useCallback( (newValue) => { // even if debounced it's triggering for empty string with the previous valid value - if (currentColumn.params.value === newValue) { + if ( + currentColumn.params.value === newValue || + !isValidNumber(newValue, false, undefined, undefined, 15) + ) { return; } // Because of upstream specific UX flows, we need fresh layer state here @@ -209,13 +214,26 @@ export const staticValueOperation: OperationDefinition< const onChangeHandler = useCallback( (e: React.ChangeEvent) => { const value = e.currentTarget.value; - handleInputChange(isValidNumber(value) ? value : undefined); + handleInputChange(value); }, [handleInputChange] ); + const inputValueIsValid = isValidNumber(inputValue, false, undefined, undefined, 15); + return ( - + { - const discoverSetupContract: LogExplorerLocatorDependencies = { - discover: { - locator: sharePluginMock.createLocator(), - }, + const logExplorerLocatorDependencies: LogExplorerLocatorDependencies = { + discoverAppLocator: sharePluginMock.createLocator(), }; - const logExplorerLocator = new LogExplorerLocatorDefinition(discoverSetupContract); + const logExplorerLocator = new LogExplorerLocatorDefinition(logExplorerLocatorDependencies); return { logExplorerLocator, - discoverGetLocation: discoverSetupContract.discover.locator?.getLocation, + discoverGetLocation: logExplorerLocatorDependencies.discoverAppLocator?.getLocation, }; }; diff --git a/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts b/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts index f0265e088a206..2cc0e192c5850 100644 --- a/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts +++ b/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts @@ -29,7 +29,7 @@ export class LogExplorerLocatorDefinition implements LocatorDefinition; } diff --git a/x-pack/plugins/log_explorer/public/plugin.ts b/x-pack/plugins/log_explorer/public/plugin.ts index 8375664c424f4..3c637b6b06caf 100644 --- a/x-pack/plugins/log_explorer/public/plugin.ts +++ b/x-pack/plugins/log_explorer/public/plugin.ts @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { LogExplorerLocatorDefinition, LogExplorerLocators } from '../common/locators'; import { createLogExplorer } from './components/log_explorer'; import { @@ -21,12 +22,14 @@ export class LogExplorerPlugin implements Plugin(DISCOVER_APP_LOCATOR); // Register Locators const logExplorerLocator = share.url.locators.create( new LogExplorerLocatorDefinition({ - discover, + discoverAppLocator, }) ); diff --git a/x-pack/plugins/log_explorer/server/plugin.ts b/x-pack/plugins/log_explorer/server/plugin.ts index 140d32a564ca4..99ec9e5caa5d2 100644 --- a/x-pack/plugins/log_explorer/server/plugin.ts +++ b/x-pack/plugins/log_explorer/server/plugin.ts @@ -5,10 +5,34 @@ * 2.0. */ -import { Plugin } from '@kbn/core/server'; +import { Plugin, CoreSetup } from '@kbn/core/server'; +import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; +import { LogExplorerLocatorDefinition, LogExplorerLocators } from '../common/locators'; +import type { LogExplorerSetupDeps } from './types'; export class LogExplorerServerPlugin implements Plugin { - setup() {} + private locators?: LogExplorerLocators; + + setup(core: CoreSetup, plugins: LogExplorerSetupDeps) { + const { share } = plugins; + const discoverAppLocator = + share.url.locators.get(DISCOVER_APP_LOCATOR); + + // Register Locators + const logExplorerLocator = share.url.locators.create( + new LogExplorerLocatorDefinition({ + discoverAppLocator, + }) + ); + + this.locators = { + logExplorerLocator, + }; + + return { + locators: this.locators, + }; + } start() {} } diff --git a/x-pack/plugins/log_explorer/server/types.ts b/x-pack/plugins/log_explorer/server/types.ts new file mode 100644 index 0000000000000..a63f08c95adc9 --- /dev/null +++ b/x-pack/plugins/log_explorer/server/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SharePluginSetup } from '@kbn/share-plugin/server'; + +export interface LogExplorerSetupDeps { + share: SharePluginSetup; +} diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js index 40ebecd135548..1afed1eaac8ca 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js @@ -28,6 +28,7 @@ import { i18n } from '@kbn/i18n'; import { useMlKibana } from '../../../contexts/kibana'; import { ML_PAGES } from '../../../../../common/constants/locator'; import { PLUGIN_ID } from '../../../../../common/constants/app'; +import { MlNodeAvailableWarningShared } from '../../node_available_warning'; const JOB_FILTER_FIELDS = ['job_id', 'groups']; const GROUP_FILTER_FIELDS = ['id']; @@ -43,11 +44,15 @@ export function JobSelectorTable({ withTimeRangeSelector, }) { const [sortableProperties, setSortableProperties] = useState(); + const [mlNodesAvailable, setMlNodesAvailable] = useState(true); const [currentTab, setCurrentTab] = useState('Jobs'); const { services: { - application: { navigateToApp }, + application: { + navigateToApp, + capabilities: { ml: mlCapabilities }, + }, }, } = useMlKibana(); @@ -258,6 +263,7 @@ export function JobSelectorTable({ return ( + {jobs.length === 0 && ( - + { + return { MlNodeAvailableWarningShared: () =>
}; +}); const props = { ganttBarWidth: 299, diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/__mocks__/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/__mocks__/kibana_context.ts index 477dda87408fd..355f66f2bf9fd 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/__mocks__/kibana_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/__mocks__/kibana_context.ts @@ -38,7 +38,15 @@ export const kibanaContextMock = { services: { uiSettings: { get: jest.fn() }, chrome: { recentlyAccessed: { add: jest.fn() } }, - application: { navigateToApp: jest.fn(), navigateToUrl: jest.fn() }, + application: { + navigateToApp: jest.fn(), + navigateToUrl: jest.fn(), + capabilities: { + ml: { + canCreateJob: true, + }, + }, + }, http: { basePath: { get: jest.fn(), diff --git a/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx b/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx index 267b00b95cca2..6c69446b8725e 100644 --- a/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx +++ b/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx @@ -42,52 +42,52 @@ export interface AddModelFlyoutProps { onSubmit: (modelId: string) => void; } +type FlyoutTabId = 'clickToDownload' | 'manualDownload'; + /** * Flyout for downloading elastic curated models and showing instructions for importing third-party models. */ export const AddModelFlyout: FC = ({ onClose, onSubmit, modelDownloads }) => { const canCreateTrainedModels = usePermissionCheck('canCreateTrainedModels'); - const isElserTabVisible = canCreateTrainedModels && modelDownloads.length > 0; + const isClickToDownloadTabVisible = canCreateTrainedModels && modelDownloads.length > 0; - const [selectedTabId, setSelectedTabId] = useState(isElserTabVisible ? 'elser' : 'thirdParty'); + const [selectedTabId, setSelectedTabId] = useState( + isClickToDownloadTabVisible ? 'clickToDownload' : 'manualDownload' + ); const tabs = useMemo(() => { return [ - ...(isElserTabVisible + ...(isClickToDownloadTabVisible ? [ { - id: 'elser', + id: 'clickToDownload' as const, name: ( - - - - - - - - + ), content: ( - + ), }, ] : []), { - id: 'thirdParty', + id: 'manualDownload' as const, name: ( ), - content: , + content: , }, ]; - }, [isElserTabVisible, modelDownloads, onSubmit]); + }, [isClickToDownloadTabVisible, modelDownloads, onSubmit]); const selectedTabContent = useMemo(() => { return tabs.find((obj) => obj.id === selectedTabId)?.content; @@ -133,15 +133,18 @@ export const AddModelFlyout: FC = ({ onClose, onSubmit, mod ); }; -interface ElserTabContentProps { +interface ClickToDownloadTabContentProps { modelDownloads: ModelItem[]; onModelDownload: (modelId: string) => void; } /** - * ELSER tab content for selecting a model to download. + * Tab content for selecting a model to download. */ -const ElserTabContent: FC = ({ modelDownloads, onModelDownload }) => { +const ClickToDownloadTabContent: FC = ({ + modelDownloads, + onModelDownload, +}) => { const { services: { docLinks }, } = useMlKibana(); @@ -157,26 +160,33 @@ const ElserTabContent: FC = ({ modelDownloads, onModelDown {modelName === 'elser' ? (
- -

- -

-
+ + + + + + +

+ +

+
+
+

- + = ({ modelDownloads, onModelDown

) : null} + {modelName === 'e5' ? ( +
+ +

+ +

+
+ +

+ + + +

+ + + + + + + + + + + + + + +
+ ) : null} + = ({ modelDownloads, onModelDown ), }} > - {models.map((model) => { + {models.map((model, index) => { return ( = ({ modelDownloads, onModelDown checked={model.model_id === selectedModelId} onChange={setSelectedModelId.bind(null, model.model_id)} /> - + {index < models.length - 1 ? : null} ); })} +
); })} @@ -279,9 +336,9 @@ const ElserTabContent: FC = ({ modelDownloads, onModelDown }; /** - * Third-party tab content for showing instructions for importing third-party models. + * Manual download tab content for showing instructions for importing third-party models. */ -const ThirdPartyTabContent: FC = () => { +const ManualDownloadTabContent: FC = () => { const { services: { docLinks }, } = useMlKibana(); diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 6d9dda39e2853..0fc27fcb33fd4 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -262,17 +262,17 @@ export const ModelsList: FC = ({ ); const forDownload = await trainedModelsApiService.getTrainedModelDownloads(); const notDownloaded: ModelItem[] = forDownload - .filter(({ name, hidden, recommended }) => { - if (recommended && idMap.has(name)) { - idMap.get(name)!.recommended = true; + .filter(({ model_id: modelId, hidden, recommended }) => { + if (recommended && idMap.has(modelId)) { + idMap.get(modelId)!.recommended = true; } - return !idMap.has(name) && !hidden; + return !idMap.has(modelId) && !hidden; }) .map((modelDefinition) => { return { - model_id: modelDefinition.name, - type: [ELASTIC_MODEL_TYPE], - tags: [ELASTIC_MODEL_TAG], + model_id: modelDefinition.model_id, + type: modelDefinition.type, + tags: modelDefinition.type?.includes(ELASTIC_MODEL_TAG) ? [ELASTIC_MODEL_TAG] : [], putModelConfig: modelDefinition.config, description: modelDefinition.description, state: MODEL_STATE.NOT_DOWNLOADED, diff --git a/x-pack/plugins/ml/public/application/services/elastic_models_service.ts b/x-pack/plugins/ml/public/application/services/elastic_models_service.ts index 2591fb6d82e7d..efc6249f9582b 100644 --- a/x-pack/plugins/ml/public/application/services/elastic_models_service.ts +++ b/x-pack/plugins/ml/public/application/services/elastic_models_service.ts @@ -5,7 +5,10 @@ * 2.0. */ -import type { ModelDefinitionResponse, GetElserOptions } from '@kbn/ml-trained-models-utils'; +import type { + ModelDefinitionResponse, + GetModelDownloadConfigOptions, +} from '@kbn/ml-trained-models-utils'; import { type TrainedModelsApiService } from './ml_api_service/trained_models'; export class ElasticModels { @@ -17,7 +20,7 @@ export class ElasticModels { * If any of the ML nodes run a different OS rather than Linux, or the CPU architecture isn't x86_64, * a portable version of the model is returned. */ - public async getELSER(options?: GetElserOptions): Promise { + public async getELSER(options?: GetModelDownloadConfigOptions): Promise { return await this.trainedModels.getElserConfig(options); } } diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts index b886f6f7df8e5..9bf880fb2b312 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts @@ -11,7 +11,10 @@ import type { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { useMemo } from 'react'; import type { HttpFetchQuery } from '@kbn/core/public'; import type { ErrorType } from '@kbn/ml-error-utils'; -import type { GetElserOptions, ModelDefinitionResponse } from '@kbn/ml-trained-models-utils'; +import type { + GetModelDownloadConfigOptions, + ModelDefinitionResponse, +} from '@kbn/ml-trained-models-utils'; import { ML_INTERNAL_BASE_PATH } from '../../../../common/constants/app'; import type { MlSavedObjectType } from '../../../../common/types/saved_objects'; import { HttpService } from '../http_service'; @@ -73,7 +76,7 @@ export function trainedModelsApiProvider(httpService: HttpService) { /** * Gets ELSER config for download based on the cluster OS and CPU architecture. */ - getElserConfig(options?: GetElserOptions) { + getElserConfig(options?: GetModelDownloadConfigOptions) { return httpService.http({ path: `${ML_INTERNAL_BASE_PATH}/trained_models/elser_config`, method: 'GET', diff --git a/x-pack/plugins/ml/public/mocks.ts b/x-pack/plugins/ml/public/mocks.ts index be18bfb1f49f1..8a2c7efefc9c5 100644 --- a/x-pack/plugins/ml/public/mocks.ts +++ b/x-pack/plugins/ml/public/mocks.ts @@ -20,7 +20,7 @@ const createElasticModelsMock = (): jest.Mocked => { }, }, description: 'Elastic Learned Sparse EncodeR v2 (Tech Preview)', - name: '.elser_model_2', + model_id: '.elser_model_2', }), } as unknown as jest.Mocked; }; diff --git a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts index 5267a95d4fb48..ff18327fdea5e 100644 --- a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts +++ b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts @@ -54,27 +54,53 @@ describe('modelsProvider', () => { config: { input: { field_names: ['text_field'] } }, description: 'Elastic Learned Sparse EncodeR v1 (Tech Preview)', hidden: true, - name: '.elser_model_1', + model_id: '.elser_model_1', version: 1, modelName: 'elser', + type: ['elastic', 'pytorch', 'text_expansion'], }, { config: { input: { field_names: ['text_field'] } }, default: true, description: 'Elastic Learned Sparse EncodeR v2', - name: '.elser_model_2', + model_id: '.elser_model_2', version: 2, modelName: 'elser', + type: ['elastic', 'pytorch', 'text_expansion'], }, { arch: 'amd64', config: { input: { field_names: ['text_field'] } }, description: 'Elastic Learned Sparse EncodeR v2, optimized for linux-x86_64', - name: '.elser_model_2_linux-x86_64', + model_id: '.elser_model_2_linux-x86_64', os: 'Linux', recommended: true, version: 2, modelName: 'elser', + type: ['elastic', 'pytorch', 'text_expansion'], + }, + { + config: { input: { field_names: ['text_field'] } }, + description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', + model_id: '.multilingual-e5-small', + default: true, + version: 1, + modelName: 'e5', + license: 'MIT', + type: ['pytorch', 'text_embedding'], + }, + { + arch: 'amd64', + config: { input: { field_names: ['text_field'] } }, + description: + 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', + model_id: '.multilingual-e5-small_linux-x86_64', + os: 'Linux', + recommended: true, + version: 1, + modelName: 'e5', + license: 'MIT', + type: ['pytorch', 'text_embedding'], }, ]); }); @@ -108,26 +134,51 @@ describe('modelsProvider', () => { config: { input: { field_names: ['text_field'] } }, description: 'Elastic Learned Sparse EncodeR v1 (Tech Preview)', hidden: true, - name: '.elser_model_1', + model_id: '.elser_model_1', version: 1, modelName: 'elser', + type: ['elastic', 'pytorch', 'text_expansion'], }, { config: { input: { field_names: ['text_field'] } }, recommended: true, description: 'Elastic Learned Sparse EncodeR v2', - name: '.elser_model_2', + model_id: '.elser_model_2', version: 2, modelName: 'elser', + type: ['elastic', 'pytorch', 'text_expansion'], }, { arch: 'amd64', config: { input: { field_names: ['text_field'] } }, description: 'Elastic Learned Sparse EncodeR v2, optimized for linux-x86_64', - name: '.elser_model_2_linux-x86_64', + model_id: '.elser_model_2_linux-x86_64', os: 'Linux', version: 2, modelName: 'elser', + type: ['elastic', 'pytorch', 'text_expansion'], + }, + { + config: { input: { field_names: ['text_field'] } }, + description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', + model_id: '.multilingual-e5-small', + recommended: true, + version: 1, + modelName: 'e5', + type: ['pytorch', 'text_embedding'], + license: 'MIT', + }, + { + arch: 'amd64', + config: { input: { field_names: ['text_field'] } }, + description: + 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', + model_id: '.multilingual-e5-small_linux-x86_64', + os: 'Linux', + version: 1, + modelName: 'e5', + type: ['pytorch', 'text_embedding'], + license: 'MIT', }, ]); }); @@ -136,7 +187,7 @@ describe('modelsProvider', () => { describe('getELSER', () => { test('provides a recommended definition by default', async () => { const result = await modelService.getELSER(); - expect(result.name).toEqual('.elser_model_2_linux-x86_64'); + expect(result.model_id).toEqual('.elser_model_2_linux-x86_64'); }); test('provides a default version if there is no recommended', async () => { @@ -162,17 +213,50 @@ describe('modelsProvider', () => { }); const result = await modelService.getELSER(); - expect(result.name).toEqual('.elser_model_2'); + expect(result.model_id).toEqual('.elser_model_2'); }); test('provides the requested version', async () => { const result = await modelService.getELSER({ version: 1 }); - expect(result.name).toEqual('.elser_model_1'); + expect(result.model_id).toEqual('.elser_model_1'); }); test('provides the requested version of a recommended architecture', async () => { const result = await modelService.getELSER({ version: 2 }); - expect(result.name).toEqual('.elser_model_2_linux-x86_64'); + expect(result.model_id).toEqual('.elser_model_2_linux-x86_64'); + }); + }); + + describe('getCuratedModelConfig', () => { + test('provides a recommended definition by default', async () => { + const result = await modelService.getCuratedModelConfig('e5'); + expect(result.model_id).toEqual('.multilingual-e5-small_linux-x86_64'); + }); + + test('provides a default version if there is no recommended', async () => { + mockCloud.cloudId = undefined; + (mockClient.asInternalUser.transport.request as jest.Mock).mockResolvedValueOnce({ + _nodes: { + total: 1, + successful: 1, + failed: 0, + }, + cluster_name: 'default', + nodes: { + yYmqBqjpQG2rXsmMSPb9pQ: { + name: 'node-0', + roles: ['ml'], + attributes: {}, + os: { + name: 'Mac OS X', + arch: 'aarch64', + }, + }, + }, + }); + + const result = await modelService.getCuratedModelConfig('e5'); + expect(result.model_id).toEqual('.multilingual-e5-small'); }); }); }); diff --git a/x-pack/plugins/ml/server/models/model_management/models_provider.ts b/x-pack/plugins/ml/server/models/model_management/models_provider.ts index e6243b38324bf..6d3ba51a9b76b 100644 --- a/x-pack/plugins/ml/server/models/model_management/models_provider.ts +++ b/x-pack/plugins/ml/server/models/model_management/models_provider.ts @@ -19,10 +19,11 @@ import type { } from '@elastic/elasticsearch/lib/api/types'; import { ELASTIC_MODEL_DEFINITIONS, - type GetElserOptions, + type GetModelDownloadConfigOptions, type ModelDefinitionResponse, } from '@kbn/ml-trained-models-utils'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; +import type { ElasticCuratedModelName } from '@kbn/ml-trained-models-utils'; import type { PipelineDefinition } from '../../../common/types/trained_models'; import type { MlClient } from '../../lib/ml_client'; import type { MLSavedObjectService } from '../../saved_objects'; @@ -52,6 +53,8 @@ interface ModelMapResult { error: null | any; } +export type GetCuratedModelConfigParams = Parameters; + export class ModelsProvider { private _transforms?: TransformGetTransformTransformSummary[]; @@ -410,8 +413,6 @@ export class ModelsProvider { } throw error; } - - return result; } /** @@ -460,7 +461,7 @@ export class ModelsProvider { const modelDefinitionMap = new Map(); - for (const [name, def] of Object.entries(ELASTIC_MODEL_DEFINITIONS)) { + for (const [modelId, def] of Object.entries(ELASTIC_MODEL_DEFINITIONS)) { const recommended = (isCloud && def.os === 'Linux' && def.arch === 'amd64') || (sameArch && !!def?.os && def?.os === osName && def?.arch === arch); @@ -470,7 +471,7 @@ export class ModelsProvider { const modelDefinitionResponse = { ...def, ...(recommended ? { recommended } : {}), - name, + model_id: modelId, }; if (modelDefinitionMap.has(modelName)) { @@ -494,14 +495,19 @@ export class ModelsProvider { } /** - * Provides an ELSER model name and configuration for download based on the current cluster architecture. - * The current default version is 2. If running on Cloud it returns the Linux x86_64 optimized version. - * If any of the ML nodes run a different OS rather than Linux, or the CPU architecture isn't x86_64, - * a portable version of the model is returned. + * Provides an appropriate model ID and configuration for download based on the current cluster architecture. + * + * @param modelName + * @param options + * @returns */ - async getELSER(options?: GetElserOptions): Promise | never { - const modelDownloadConfig = await this.getModelDownloads(); - + async getCuratedModelConfig( + modelName: ElasticCuratedModelName, + options?: GetModelDownloadConfigOptions + ): Promise | never { + const modelDownloadConfig = (await this.getModelDownloads()).filter( + (model) => model.modelName === modelName + ); let requestedModel: ModelDefinitionResponse | undefined; let recommendedModel: ModelDefinitionResponse | undefined; let defaultModel: ModelDefinitionResponse | undefined; @@ -527,6 +533,18 @@ export class ModelsProvider { return requestedModel || recommendedModel || defaultModel!; } + /** + * Provides an ELSER model name and configuration for download based on the current cluster architecture. + * The current default version is 2. If running on Cloud it returns the Linux x86_64 optimized version. + * If any of the ML nodes run a different OS rather than Linux, or the CPU architecture isn't x86_64, + * a portable version of the model is returned. + */ + async getELSER( + options?: GetModelDownloadConfigOptions + ): Promise | never { + return await this.getCuratedModelConfig('elser', options); + } + /** * Puts the requested ELSER model into elasticsearch, triggering elasticsearch to download the model. * Assigns the model to the * space. @@ -535,7 +553,7 @@ export class ModelsProvider { */ async installElasticModel(modelId: string, mlSavedObjectService: MLSavedObjectService) { const availableModels = await this.getModelDownloads(); - const model = availableModels.find((m) => m.name === modelId); + const model = availableModels.find((m) => m.model_id === modelId); if (!model) { throw Boom.notFound('Model not found'); } @@ -556,7 +574,7 @@ export class ModelsProvider { } const putResponse = await this._mlClient.putTrainedModel({ - model_id: model.name, + model_id: model.model_id, body: model.config, }); diff --git a/x-pack/plugins/ml/server/shared_services/providers/__mocks__/trained_models.ts b/x-pack/plugins/ml/server/shared_services/providers/__mocks__/trained_models.ts index 9af448058ce83..fa37f3d468fc3 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/__mocks__/trained_models.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/__mocks__/trained_models.ts @@ -16,7 +16,8 @@ const trainedModelsServiceMock = { deleteTrainedModel: jest.fn(), updateTrainedModelDeployment: jest.fn(), putTrainedModel: jest.fn(), - getELSER: jest.fn().mockResolvedValue({ name: '' }), + getELSER: jest.fn().mockResolvedValue({ model_id: '.elser_model_2' }), + getCuratedModelConfig: jest.fn().mockResolvedValue({ model_id: '.elser_model_2' }), } as jest.Mocked; export const createTrainedModelsProviderMock = () => diff --git a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts index 4a1edbbcb3e4d..6b04a3e7580d9 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts @@ -8,7 +8,10 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; -import type { GetElserOptions, ModelDefinitionResponse } from '@kbn/ml-trained-models-utils'; +import type { + GetModelDownloadConfigOptions, + ModelDefinitionResponse, +} from '@kbn/ml-trained-models-utils'; import type { MlInferTrainedModelRequest, MlStopTrainedModelDeploymentRequest, @@ -16,6 +19,7 @@ import type { UpdateTrainedModelDeploymentResponse, } from '../../lib/ml_client/types'; import { modelsProvider } from '../../models/model_management'; +import type { GetCuratedModelConfigParams } from '../../models/model_management/models_provider'; import type { GetGuards } from '../shared_services'; export interface TrainedModelsProvider { @@ -47,7 +51,8 @@ export interface TrainedModelsProvider { putTrainedModel( params: estypes.MlPutTrainedModelRequest ): Promise; - getELSER(params?: GetElserOptions): Promise; + getELSER(params?: GetModelDownloadConfigOptions): Promise; + getCuratedModelConfig(...params: GetCuratedModelConfigParams): Promise; }; } @@ -123,7 +128,7 @@ export function getTrainedModelsProvider( return mlClient.putTrainedModel(params); }); }, - async getELSER(params?: GetElserOptions) { + async getELSER(params?: GetModelDownloadConfigOptions) { return await guards .isFullLicense() .hasMlCapabilities(['canGetTrainedModels']) @@ -131,6 +136,14 @@ export function getTrainedModelsProvider( return modelsProvider(scopedClient, mlClient, cloud).getELSER(params); }); }, + async getCuratedModelConfig(...params: GetCuratedModelConfigParams) { + return await guards + .isFullLicense() + .hasMlCapabilities(['canGetTrainedModels']) + .ok(async ({ scopedClient, mlClient }) => { + return modelsProvider(scopedClient, mlClient, cloud).getCuratedModelConfig(...params); + }); + }, }; }, }; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts b/x-pack/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts new file mode 100644 index 0000000000000..658d1debe0a94 --- /dev/null +++ b/x-pack/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts @@ -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 { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; +import type { TimeRange } from '@kbn/es-query'; +import type { LocatorPublic } from '@kbn/share-plugin/common'; +import type { CustomThresholdExpressionMetric } from './types'; + +export const getViewInAppUrl = ( + metrics: CustomThresholdExpressionMetric[], + startedAt?: string, + logExplorerLocator?: LocatorPublic, + filter?: string, + dataViewId?: string, + endedAt?: string +) => { + if (!logExplorerLocator) return ''; + + let timeRange: TimeRange | undefined; + if (startedAt) { + timeRange = getPaddedAlertTimeRange(startedAt, endedAt); + timeRange.to = endedAt ? timeRange.to : 'now'; + } + + const query = { + query: '', + language: 'kuery', + }; + const isOneCountConditionWithFilter = + metrics.length === 1 && metrics[0].aggType === 'count' && metrics[0].filter; + if (filter && isOneCountConditionWithFilter) { + query.query = `${filter} and ${metrics[0].filter}`; + } else if (isOneCountConditionWithFilter) { + query.query = metrics[0].filter!; + } else if (filter) { + query.query = filter; + } + + return logExplorerLocator?.getRedirectUrl({ + dataset: dataViewId, + timeRange, + query, + }); +}; diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index 083f1956ec31f..96bfbb4738b2e 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -35,6 +35,7 @@ "visualizations", "dashboard", "expressions", + "logExplorer", "licensing" ], "optionalPlugins": [ diff --git a/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_footer.tsx b/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_footer.tsx index 27cbd3a62fdc6..324a96845137a 100644 --- a/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_footer.tsx +++ b/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_footer.tsx @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; + +import React, { useState, useEffect } from 'react'; import { EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibana } from '../../utils/kibana_react'; @@ -25,17 +26,22 @@ export function AlertsFlyoutFooter({ alert, isInApp }: FlyoutProps & { isInApp: }, } = useKibana().services; const { config } = usePluginContext(); + const [viewInAppUrl, setViewInAppUrl] = useState(); + + useEffect(() => { + if (!alert.hasBasePath) { + setViewInAppUrl(prepend(alert.link ?? '')); + } else { + setViewInAppUrl(alert.link); + } + }, [alert.hasBasePath, alert.link, prepend]); return ( {!alert.link || isInApp ? null : ( - + {i18n.translate('xpack.observability.alertsFlyout.viewInAppButtonText', { defaultMessage: 'View in app', })} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx index d1ab267f1ce23..8747558251da3 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx @@ -24,6 +24,9 @@ const mockedChartStartContract = chartPluginMock.createStartContract(); jest.mock('@kbn/observability-alert-details', () => ({ AlertAnnotation: () => {}, AlertActiveTimeRangeAnnotation: () => {}, +})); + +jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ getPaddedAlertTimeRange: () => ({ from: '2023-03-28T10:43:13.802Z', to: '2023-03-29T13:14:09.581Z', diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx index deebe5a6cd4ca..c9b17b4f48c92 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx @@ -22,11 +22,8 @@ import { } from '@elastic/eui'; import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils'; import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common'; -import { - AlertAnnotation, - getPaddedAlertTimeRange, - AlertActiveTimeRangeAnnotation, -} from '@kbn/observability-alert-details'; +import { AlertAnnotation, AlertActiveTimeRangeAnnotation } from '@kbn/observability-alert-details'; +import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import { DataView } from '@kbn/data-views-plugin/common'; import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; import { useKibana } from '../../../utils/kibana_react'; diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx index 9a3bce0a9a326..0f2531f6ff793 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx @@ -14,19 +14,17 @@ import { EuiToolTip, } from '@elastic/eui'; -import React, { useMemo, useState, useCallback } from 'react'; +import React, { useMemo, useState, useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; import { AttachmentType } from '@kbn/cases-plugin/common'; import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TimelineNonEcsData } from '@kbn/timelines-plugin/common'; import { - ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_UUID, - OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, } from '@kbn/rule-data-utils'; import { useBulkUntrackAlerts } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '../../../utils/kibana_react'; @@ -70,6 +68,7 @@ export function AlertActions({ } = useKibana().services; const { mutateAsync: untrackAlerts } = useBulkUntrackAlerts(); const userCasesPermissions = canUseCases([observabilityFeatureId]); + const [viewInAppUrl, setViewInAppUrl] = useState(); const parseObservabilityAlert = useMemo( () => parseAlert(observabilityRuleTypeRegistry), @@ -79,6 +78,14 @@ export function AlertActions({ const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {}); const alert = parseObservabilityAlert(dataFieldEs); + useEffect(() => { + if (!alert.hasBasePath) { + setViewInAppUrl(prepend(alert.link ?? '')); + } else { + setViewInAppUrl(alert.link); + } + }, [alert.hasBasePath, alert.link, prepend]); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const ruleId = alert.fields[ALERT_RULE_UUID] ?? null; @@ -236,10 +243,7 @@ export function AlertActions({ return ( <> - {/* Hide the View In App for the Threshold alerts, temporarily https://github.com/elastic/kibana/pull/159915 */} - {alert.fields[ALERT_RULE_TYPE_ID] === OBSERVABILITY_THRESHOLD_RULE_TYPE_ID ? ( - - ) : ( + {viewInAppUrl ? ( + ) : ( + )} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/badges_portal.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/badges_portal.tsx new file mode 100644 index 0000000000000..aace661240d23 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/badges_portal.tsx @@ -0,0 +1,34 @@ +/* + * 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, { ReactNode, useEffect, useMemo } from 'react'; +import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; +import ReactDOM from 'react-dom'; +export interface Props { + children: ReactNode; + containerRef: React.RefObject; +} + +export function SloCardBadgesPortal({ children, containerRef }: Props) { + const portalNode = useMemo(() => createHtmlPortalNode(), []); + + useEffect(() => { + if (containerRef?.current) { + setTimeout(() => { + const gapDiv = containerRef?.current?.querySelector('.echMetricText__gap'); + if (!gapDiv) return; + ReactDOM.render(, gapDiv); + }, 100); + } + + return () => { + portalNode.unmount(); + }; + }, [portalNode, containerRef]); + + return {children}; +} 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 index 07ad3caf1fd00..ac649a71e3600 100644 --- 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 @@ -9,16 +9,17 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { Chart, + DARK_THEME, isMetricElementEvent, Metric, - Settings, - DARK_THEME, MetricTrendShape, + Settings, } from '@elastic/charts'; import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui'; -import { SLOWithSummaryResponse, HistoricalSummaryResponse, ALL_VALUE } from '@kbn/slo-schema'; +import { ALL_VALUE, HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import { i18n } from '@kbn/i18n'; +import { SloCardBadgesPortal } from './badges_portal'; import { useSloListActions } from '../../hooks/use_slo_list_actions'; import { BurnRateRuleFlyout } from '../common/burn_rate_rule_flyout'; import { formatHistoricalData } from '../../../../utils/slo/chart_data_formatter'; @@ -52,12 +53,7 @@ const useCardColor = (status?: SLOWithSummaryResponse['summary']['status']) => { }; 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 ? '..' : ''); + return slo.groupBy && slo.groupBy !== ALL_VALUE ? `${slo.groupBy}: ${slo.instanceId}` : ''; }; export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, cardsPerRow }: Props) { @@ -65,6 +61,8 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, cards application: { navigateToUrl }, } = useKibana().services; + const containerRef = React.useRef(null); + const [isMouseOver, setIsMouseOver] = useState(false); const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false); @@ -88,6 +86,7 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, cards return ( <> } onMouseOver={() => { if (!isMouseOver) { setIsMouseOver(true); @@ -145,13 +144,6 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, cards ]} /> - {(isMouseOver || isActionsPopoverOpen) && ( )} + + + void; } -const Container = styled.div<{ hasGroupBy: boolean }>` - position: absolute; +const Container = styled.div` display: inline-block; - top: ${({ hasGroupBy }) => (hasGroupBy ? '55px' : '35px')}; - left: 7px; - z-index: 1; - border-radius: ${({ theme }) => theme.eui.euiBorderRadius}; + margin-top: 5px; `; -export function SloCardItemBadges({ - slo, - activeAlerts, - rules, - handleCreateRule, - hasGroupBy, -}: Props) { +export function SloCardItemBadges({ slo, activeAlerts, rules, handleCreateRule }: 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 index 3768bdbb7dac3..4e4df93967fde 100644 --- 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 @@ -6,7 +6,13 @@ */ import React from 'react'; -import { EuiFlexGrid, EuiFlexItem, EuiPanel, EuiSkeletonText } from '@elastic/eui'; +import { + EuiFlexGrid, + EuiFlexItem, + EuiPanel, + EuiSkeletonText, + useIsWithinBreakpoints, +} 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'; @@ -23,6 +29,25 @@ export interface Props { rulesBySlo?: UseFetchRulesForSloResponse['data']; } +const useColumns = (cardsPerRow: string | undefined) => { + const isMobile = useIsWithinBreakpoints(['xs', 's']); + const isMedium = useIsWithinBreakpoints(['m']); + const isLarge = useIsWithinBreakpoints(['l']); + + const columns = (Number(cardsPerRow) as EuiFlexGridProps['columns']) ?? 3; + + switch (true) { + case isMobile: + return 1; + case isMedium: + return columns > 2 ? 2 : columns; + case isLarge: + return columns > 3 ? 3 : columns; + default: + return columns; + } +}; + export function SloListCardView({ sloList, loading, @@ -36,12 +61,14 @@ export function SloListCardView({ list: sloList.map((slo) => ({ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE })), }); + const columns = useColumns(cardsPerRow); + if (loading && sloList.length === 0) { return ; } return ( - + {sloList.map((slo) => ( ; - export interface ObservabilityPublicPluginsSetup { data: DataPublicPluginSetup; observabilityShared: ObservabilitySharedPluginSetup; @@ -117,7 +117,6 @@ export interface ObservabilityPublicPluginsSetup { embeddable: EmbeddableSetup; licensing: LicensingPluginSetup; } - export interface ObservabilityPublicPluginsStart { actionTypeRegistry: ActionTypeRegistryContract; cases: CasesUiStart; @@ -146,7 +145,6 @@ export interface ObservabilityPublicPluginsStart { aiops: AiopsPluginStart; serverless?: ServerlessPluginStart; } - export type ObservabilityPublicStart = ReturnType; export class Plugin @@ -239,6 +237,9 @@ export class Plugin const sloEditLocator = pluginsSetup.share.url.locators.create(new SloEditLocatorDefinition()); const sloListLocator = pluginsSetup.share.url.locators.create(new SloListLocatorDefinition()); + const logExplorerLocator = + pluginsSetup.share.url.locators.get(LOG_EXPLORER_LOCATOR_ID); + const mount = async (params: AppMountParameters) => { // Load application bundle const { renderApp } = await import('./application'); @@ -292,7 +293,7 @@ export class Plugin coreSetup.application.register(app); - registerObservabilityRuleTypes(config, this.observabilityRuleTypeRegistry); + registerObservabilityRuleTypes(config, this.observabilityRuleTypeRegistry, logExplorerLocator); const assertPlatinumLicense = async () => { const licensing = await pluginsSetup.licensing; diff --git a/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts b/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts index aef7c5eca6ce2..76c3d8c89662c 100644 --- a/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts +++ b/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts @@ -16,7 +16,7 @@ import { AsDuration, AsPercent } from '../../common/utils/formatters'; export type ObservabilityRuleTypeFormatter = (options: { fields: ParsedTechnicalFields & Record; formatters: { asDuration: AsDuration; asPercent: AsPercent }; -}) => { reason: string; link?: string }; +}) => { reason: string; link?: string; hasBasePath?: boolean }; export interface ObservabilityRuleTypeModel extends RuleTypeModel { diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index d807de2499c50..a1faec27f6c83 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -7,15 +7,24 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { ALERT_REASON, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; - +import type { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { + ALERT_REASON, + ALERT_RULE_PARAMETERS, + ALERT_START, + OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, +} from '@kbn/rule-data-utils'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; +import type { LocatorPublic } from '@kbn/share-plugin/common'; +import type { MetricExpression } from '../components/custom_threshold/types'; +import type { CustomThresholdExpressionMetric } from '../../common/custom_threshold_rule/types'; +import { getViewInAppUrl } from '../../common/custom_threshold_rule/get_view_in_app_url'; import { SLO_ID_FIELD, SLO_INSTANCE_ID_FIELD } from '../../common/field_names/slo'; import { ConfigSchema } from '../plugin'; import { ObservabilityRuleTypeRegistry } from './create_observability_rule_type_registry'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../common/constants'; import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation'; import { validateCustomThreshold } from '../components/custom_threshold/components/validation'; -import { formatReason } from '../components/custom_threshold/rule_data_formatters'; const sloBurnRateDefaultActionMessage = i18n.translate( 'xpack.observability.slo.rules.burnRate.defaultActionMessage', @@ -71,9 +80,15 @@ const thresholdDefaultRecoveryMessage = i18n.translate( } ); -export const registerObservabilityRuleTypes = ( +const getDataViewId = (searchConfiguration?: SerializedSearchSourceFields) => + typeof searchConfiguration?.index === 'string' + ? searchConfiguration.index + : searchConfiguration?.index?.title; + +export const registerObservabilityRuleTypes = async ( config: ConfigSchema, - observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry + observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry, + logExplorerLocator?: LocatorPublic ) => { observabilityRuleTypeRegistry.register({ id: SLO_BURN_RATE_RULE_TYPE_ID, @@ -121,7 +136,27 @@ export const registerObservabilityRuleTypes = ( defaultActionMessage: thresholdDefaultActionMessage, defaultRecoveryMessage: thresholdDefaultRecoveryMessage, requiresAppContext: false, - format: formatReason, + format: ({ fields }) => { + const searchConfiguration = fields[ALERT_RULE_PARAMETERS]?.searchConfiguration as + | SerializedSearchSourceFields + | undefined; + const criteria = fields[ALERT_RULE_PARAMETERS]?.criteria as MetricExpression[]; + const metrics: CustomThresholdExpressionMetric[] = + criteria.length === 1 ? criteria[0].metrics : []; + + const dataViewId = getDataViewId(searchConfiguration); + return { + reason: fields[ALERT_REASON] ?? '-', + link: getViewInAppUrl( + metrics, + fields[ALERT_START], + logExplorerLocator, + (searchConfiguration?.query as { query: string }).query, + dataViewId + ), + hasBasePath: true, + }; + }, alertDetailsAppSection: lazy( () => import('../components/custom_threshold/components/alert_details_app_section') ), diff --git a/x-pack/plugins/observability/public/typings/alerts.ts b/x-pack/plugins/observability/public/typings/alerts.ts index 2e5dfe3ca86f2..45a44169121f7 100644 --- a/x-pack/plugins/observability/public/typings/alerts.ts +++ b/x-pack/plugins/observability/public/typings/alerts.ts @@ -15,4 +15,5 @@ export interface TopAlert = {} reason: string; link?: string; active: boolean; + hasBasePath?: boolean; } diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index c2340fbc046fb..b769f5f32b73b 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -29,6 +29,9 @@ import { } from '../../../../common/custom_threshold_rule/types'; jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); +jest.mock('../../../../common/custom_threshold_rule/get_view_in_app_url', () => ({ + getViewInAppUrl: () => 'mockedViewInApp', +})); interface AlertTestInstance { instance: AlertInstanceMock; @@ -134,7 +137,7 @@ const setEvaluationResults = (response: Array>) => { jest.requireMock('./lib/evaluate_rule').evaluateRule.mockImplementation(() => response); }; -describe('The metric threshold alert type', () => { +describe('The custom threshold alert type', () => { describe('querying the entire infrastructure', () => { afterAll(() => clearInstances()); const instanceID = '*'; @@ -1339,6 +1342,7 @@ describe('The metric threshold alert type', () => { timestamp: STARTED_AT_MOCK_DATE.toISOString(), value: ['[NO DATA]', null], tags: [], + viewInAppUrl: 'mockedViewInApp', }); expect(recentAction).toBeNoDataAction(); }); @@ -1765,6 +1769,7 @@ const mockLibs: any = { groupByPageSize: 10_000, }, }, + locators: {}, }; const executor = createCustomThresholdExecutor(mockLibs); @@ -1780,6 +1785,7 @@ const mockedIndex = { }; const mockedDataView = { getIndexPattern: () => 'mockedIndexPattern', + getName: () => 'mockedDataViewName', ...mockedIndex, }; const mockedSearchSource = { diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 2e50c33048f51..ac052a6b8f4d3 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -6,6 +6,7 @@ */ import { isEqual } from 'lodash'; +import { LogExplorerLocatorParams } from '@kbn/deeplinks-observability'; import { ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES, @@ -17,6 +18,7 @@ import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; import { IBasePath, Logger } from '@kbn/core/server'; import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server'; import { AlertsLocatorParams, getAlertUrl } from '../../../../common'; +import { getViewInAppUrl } from '../../../../common/custom_threshold_rule/get_view_in_app_url'; import { ObservabilityConfig } from '../../..'; import { FIRED_ACTIONS_ID, NO_DATA_ACTIONS_ID, UNGROUPED_FACTORY_KEY } from './constants'; import { @@ -48,16 +50,21 @@ import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; import { MissingGroupsRecord } from './lib/check_missing_group'; import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record'; +export interface CustomThresholdLocators { + alertsLocator?: LocatorPublic; + logExplorerLocator?: LocatorPublic; +} + export const createCustomThresholdExecutor = ({ - alertsLocator, basePath, logger, config, + locators: { alertsLocator, logExplorerLocator }, }: { basePath: IBasePath; logger: Logger; config: ObservabilityConfig; - alertsLocator?: LocatorPublic; + locators: CustomThresholdLocators; }): LifecycleRuleExecutor< CustomThresholdRuleParams, CustomThresholdRuleTypeState, @@ -132,21 +139,22 @@ export const createCustomThresholdExecutor = ({ : []; const initialSearchSource = await searchSourceClient.create(params.searchConfiguration!); - const dataView = initialSearchSource.getField('index')!.getIndexPattern(); - const dataViewName = initialSearchSource.getField('index')!.name; - const timeFieldName = initialSearchSource.getField('index')?.timeFieldName; - if (!dataView) { + const dataView = initialSearchSource.getField('index')!; + const { id: dataViewId, timeFieldName } = dataView; + const dataViewIndexPattern = dataView.getIndexPattern(); + const dataViewName = dataView.getName(); + if (!dataViewIndexPattern) { throw new Error('No matched data view'); } else if (!timeFieldName) { throw new Error('The selected data view does not have a timestamp field'); } - // Calculate initial start and end date with no time window, as each criteria has it's own time window + // Calculate initial start and end date with no time window, as each criterion has its own time window const { dateStart, dateEnd } = getTimeRange(); const alertResults = await evaluateRule( services.scopedClusterClient.asCurrentUser, params as EvaluatedRuleParams, - dataView, + dataViewIndexPattern, timeFieldName, compositeSize, alertOnGroupDisappear, @@ -270,13 +278,20 @@ export const createCustomThresholdExecutor = ({ group: groupByKeysObjectMapping[group], reason, timestamp, - value: alertResults.map((result, index) => { + value: alertResults.map((result) => { const evaluation = result[group]; if (!evaluation) { return null; } return formatAlertResult(evaluation).currentValue; }), + viewInAppUrl: getViewInAppUrl( + alertResults.length === 1 ? alertResults[0][group].metrics : [], + indexedStartedAt, + logExplorerLocator, + params.searchConfiguration.query.query, + params.searchConfiguration?.index?.title ?? dataViewId + ), ...additionalContext, }); } diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index db4402dca7e88..9f911eabc1f9a 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -16,13 +16,8 @@ import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import { LicenseType } from '@kbn/licensing-plugin/server'; -import { LocatorPublic } from '@kbn/share-plugin/common'; import { EsQueryRuleParamsExtractedParams } from '@kbn/stack-alerts-plugin/server/rule_types/es_query/rule_type_params'; -import { - AlertsLocatorParams, - observabilityFeatureId, - observabilityPaths, -} from '../../../../common'; +import { observabilityFeatureId, observabilityPaths } from '../../../../common'; import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants'; @@ -38,9 +33,13 @@ import { tagsActionVariableDescription, timestampActionVariableDescription, valueActionVariableDescription, + viewInAppUrlActionVariableDescription, } from './translations'; import { oneOfLiterals, validateKQLStringFilter } from './utils'; -import { createCustomThresholdExecutor } from './custom_threshold_executor'; +import { + createCustomThresholdExecutor, + CustomThresholdLocators, +} from './custom_threshold_executor'; import { FIRED_ACTION, NO_DATA_ACTION } from './constants'; import { ObservabilityConfig } from '../../..'; @@ -69,7 +68,7 @@ export function thresholdRuleType( config: ObservabilityConfig, logger: Logger, ruleDataClient: IRuleDataClient, - alertsLocator?: LocatorPublic + locators: CustomThresholdLocators ) { const baseCriterion = { threshold: schema.arrayOf(schema.number()), @@ -128,7 +127,7 @@ export function thresholdRuleType( minimumLicenseRequired: 'basic' as LicenseType, isExportable: true, executor: createLifecycleRuleExecutor( - createCustomThresholdExecutor({ alertsLocator, basePath, logger, config }) + createCustomThresholdExecutor({ basePath, logger, config, locators }) ), doesSetRecoveryContext: true, actionVariables: { @@ -148,6 +147,7 @@ export function thresholdRuleType( { name: 'orchestrator', description: orchestratorActionVariableDescription }, { name: 'labels', description: labelsActionVariableDescription }, { name: 'tags', description: tagsActionVariableDescription }, + { name: 'viewInAppUrl', description: viewInAppUrlActionVariableDescription }, ], }, useSavedObjectReferences: { diff --git a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts index 0e1abfc1037d7..c90ee35f86552 100644 --- a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts +++ b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts @@ -7,7 +7,6 @@ import { PluginSetupContract } from '@kbn/alerting-plugin/server'; import { IBasePath, Logger } from '@kbn/core/server'; -import { LocatorPublic } from '@kbn/share-plugin/common'; import { createLifecycleExecutor, Dataset, @@ -15,7 +14,8 @@ import { } from '@kbn/rule-registry-plugin/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; -import { sloFeatureId, AlertsLocatorParams, observabilityFeatureId } from '../../../common'; +import { CustomThresholdLocators } from './custom_threshold/custom_threshold_executor'; +import { sloFeatureId, observabilityFeatureId } from '../../../common'; import { ObservabilityConfig } from '../..'; import { SLO_RULE_REGISTRATION_CONTEXT, @@ -27,11 +27,11 @@ import { sloRuleFieldMap } from './slo_burn_rate/field_map'; export function registerRuleTypes( alertingPlugin: PluginSetupContract, - logger: Logger, - ruleDataService: IRuleDataService, basePath: IBasePath, config: ObservabilityConfig, - alertsLocator?: LocatorPublic + logger: Logger, + ruleDataService: IRuleDataService, + locators: CustomThresholdLocators ) { // SLO RULE const ruleDataClientSLO = ruleDataService.initializeIndex({ @@ -55,7 +55,7 @@ export function registerRuleTypes( ruleDataClientSLO ); alertingPlugin.registerType( - sloBurnRateRuleType(createLifecycleRuleExecutorSLO, basePath, alertsLocator) + sloBurnRateRuleType(createLifecycleRuleExecutorSLO, basePath, locators.alertsLocator) ); // Threshold RULE @@ -85,7 +85,7 @@ export function registerRuleTypes( config, logger, ruleDataClientThreshold, - alertsLocator + locators ) ); } diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index d1ca0e45b74a2..6726b8abfe178 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -18,6 +18,7 @@ import { Plugin, PluginInitializerContext, } from '@kbn/core/server'; +import { LOG_EXPLORER_LOCATOR_ID, LogExplorerLocatorParams } from '@kbn/deeplinks-observability'; import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; @@ -101,6 +102,8 @@ export class ObservabilityPlugin implements Plugin { const config = this.initContext.config.get(); const alertsLocator = plugins.share.url.locators.create(new AlertsLocatorDefinition()); + const logExplorerLocator = + plugins.share.url.locators.get(LOG_EXPLORER_LOCATOR_ID); plugins.features.registerKibanaFeature({ id: casesFeatureId, @@ -332,14 +335,10 @@ export class ObservabilityPlugin implements Plugin { core.savedObjects.registerType(slo); core.savedObjects.registerType(threshold); - registerRuleTypes( - plugins.alerting, - this.logger, - ruleDataService, - core.http.basePath, - config, - alertsLocator - ); + registerRuleTypes(plugins.alerting, core.http.basePath, config, this.logger, ruleDataService, { + alertsLocator, + logExplorerLocator, + }); registerSloUsageCollector(plugins.usageCollection); core.getStartServices().then(([coreStart, pluginStart]) => { diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index f2bd514a4c4ef..15e2aade480c7 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -71,6 +71,7 @@ "@kbn/rison", "@kbn/io-ts-utils", "@kbn/observability-alert-details", + "@kbn/observability-get-padded-alert-time-range-util", "@kbn/ui-actions-plugin", "@kbn/field-types", "@kbn/safer-lodash-set", diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index be24975deae6f..e79caa6393975 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -293,6 +293,11 @@ it('matches snapshot', () => { "required": true, "type": "keyword", }, + "kibana.alert.workflow_assignee_ids": Object { + "array": true, + "required": false, + "type": "keyword", + }, "kibana.alert.workflow_reason": Object { "array": false, "required": false, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/index.ts new file mode 100644 index 0000000000000..b74132faed031 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './set_alert_assignees_route.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/mocks.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/mocks.ts new file mode 100644 index 0000000000000..ef668dc36d421 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/mocks.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './set_alert_assignees_route.mock'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.gen.ts new file mode 100644 index 0000000000000..f2b2be478ced3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.gen.ts @@ -0,0 +1,46 @@ +/* + * 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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +import { NonEmptyString } from '../model/rule_schema/common_attributes.gen'; + +export type AlertAssignees = z.infer; +export const AlertAssignees = z.object({ + /** + * A list of users ids to assign. + */ + add: z.array(NonEmptyString), + /** + * A list of users ids to unassign. + */ + remove: z.array(NonEmptyString), +}); + +/** + * A list of alerts ids. + */ +export type AlertIds = z.infer; +export const AlertIds = z.array(NonEmptyString).min(1); + +export type SetAlertAssigneesRequestBody = z.infer; +export const SetAlertAssigneesRequestBody = z.object({ + /** + * Details about the assignees to assign and unassign. + */ + assignees: AlertAssignees, + /** + * List of alerts ids to assign and unassign passed assignees. + */ + ids: AlertIds, +}); +export type SetAlertAssigneesRequestBodyInput = z.input; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.mock.ts new file mode 100644 index 0000000000000..9c41e2eae8058 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.mock.ts @@ -0,0 +1,17 @@ +/* + * 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 { SetAlertAssigneesRequestBody } from './set_alert_assignees_route.gen'; + +export const getSetAlertAssigneesRequestMock = ( + assigneesToAdd: string[] = [], + assigneesToRemove: string[] = [], + ids: string[] = [] +): SetAlertAssigneesRequestBody => ({ + assignees: { add: assigneesToAdd, remove: assigneesToRemove }, + ids, +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml new file mode 100644 index 0000000000000..6c3663402118a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml @@ -0,0 +1,58 @@ +openapi: 3.0.0 +info: + title: Assign alerts API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/assignees: + summary: Assigns users to alerts + post: + operationId: SetAlertAssignees + x-codegen-enabled: true + description: Assigns users to alerts. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - assignees + - ids + properties: + assignees: + $ref: '#/components/schemas/AlertAssignees' + description: Details about the assignees to assign and unassign. + ids: + $ref: '#/components/schemas/AlertIds' + description: List of alerts ids to assign and unassign passed assignees. + responses: + 200: + description: Indicates a successful call. + 400: + description: Invalid request. + +components: + schemas: + AlertAssignees: + type: object + required: + - add + - remove + properties: + add: + type: array + items: + $ref: '../model/rule_schema/common_attributes.schema.yaml#/components/schemas/NonEmptyString' + description: A list of users ids to assign. + remove: + type: array + items: + $ref: '../model/rule_schema/common_attributes.schema.yaml#/components/schemas/NonEmptyString' + description: A list of users ids to unassign. + + AlertIds: + type: array + items: + $ref: '../model/rule_schema/common_attributes.schema.yaml#/components/schemas/NonEmptyString' + minItems: 1 + description: A list of alerts ids. diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/index.ts index eadf1e48e9e31..56c6d4225f745 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export * from './alert_assignees'; export * from './alert_tags'; export * from './fleet_integrations'; export * from './index_management'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/8.12.0/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/8.12.0/index.ts new file mode 100644 index 0000000000000..da97667035a66 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/8.12.0/index.ts @@ -0,0 +1,56 @@ +/* + * 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 { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; +import type { AlertWithCommonFields800 } from '@kbn/rule-registry-plugin/common/schemas/8.0.0'; +import type { + Ancestor890, + BaseFields890, + EqlBuildingBlockFields890, + EqlShellFields890, + NewTermsFields890, +} from '../8.9.0'; + +/* DO NOT MODIFY THIS SCHEMA TO ADD NEW FIELDS. These types represent the alerts that shipped in 8.12.0. +Any changes to these types should be bug fixes so the types more accurately represent the alerts from 8.12.0. +If you are adding new fields for a new release of Kibana, create a new sibling folder to this one +for the version to be released and add the field(s) to the schema in that folder. +Then, update `../index.ts` to import from the new folder that has the latest schemas, add the +new schemas to the union of all alert schemas, and re-export the new schemas as the `*Latest` schemas. +*/ + +export type { Ancestor890 as Ancestor8120 }; + +export interface BaseFields8120 extends BaseFields890 { + [ALERT_WORKFLOW_ASSIGNEE_IDS]: string[] | undefined; +} + +export interface WrappedFields8120 { + _id: string; + _index: string; + _source: T; +} + +export type GenericAlert8120 = AlertWithCommonFields800; + +export type EqlShellFields8120 = EqlShellFields890 & BaseFields8120; + +export type EqlBuildingBlockFields8120 = EqlBuildingBlockFields890 & BaseFields8120; + +export type NewTermsFields8120 = NewTermsFields890 & BaseFields8120; + +export type NewTermsAlert8120 = NewTermsFields890 & BaseFields8120; + +export type EqlBuildingBlockAlert8120 = AlertWithCommonFields800; + +export type EqlShellAlert8120 = AlertWithCommonFields800; + +export type DetectionAlert8120 = + | GenericAlert8120 + | EqlShellAlert8120 + | EqlBuildingBlockAlert8120 + | NewTermsAlert8120; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/index.ts index d3718c4f07db9..742e5fd4ecfc1 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/alerts/index.ts @@ -11,15 +11,16 @@ import type { DetectionAlert840 } from './8.4.0'; import type { DetectionAlert860 } from './8.6.0'; import type { DetectionAlert870 } from './8.7.0'; import type { DetectionAlert880 } from './8.8.0'; +import type { DetectionAlert890 } from './8.9.0'; import type { - Ancestor890, - BaseFields890, - DetectionAlert890, - EqlBuildingBlockFields890, - EqlShellFields890, - NewTermsFields890, - WrappedFields890, -} from './8.9.0'; + Ancestor8120, + BaseFields8120, + DetectionAlert8120, + EqlBuildingBlockFields8120, + EqlShellFields8120, + NewTermsFields8120, + WrappedFields8120, +} from './8.12.0'; // When new Alert schemas are created for new Kibana versions, add the DetectionAlert type from the new version // here, e.g. `export type DetectionAlert = DetectionAlert800 | DetectionAlert820` if a new schema is created in 8.2.0 @@ -29,14 +30,15 @@ export type DetectionAlert = | DetectionAlert860 | DetectionAlert870 | DetectionAlert880 - | DetectionAlert890; + | DetectionAlert890 + | DetectionAlert8120; export type { - Ancestor890 as AncestorLatest, - BaseFields890 as BaseFieldsLatest, - DetectionAlert890 as DetectionAlertLatest, - WrappedFields890 as WrappedFieldsLatest, - EqlBuildingBlockFields890 as EqlBuildingBlockFieldsLatest, - EqlShellFields890 as EqlShellFieldsLatest, - NewTermsFields890 as NewTermsFieldsLatest, + Ancestor8120 as AncestorLatest, + BaseFields8120 as BaseFieldsLatest, + DetectionAlert8120 as DetectionAlertLatest, + WrappedFields8120 as WrappedFieldsLatest, + EqlBuildingBlockFields8120 as EqlBuildingBlockFieldsLatest, + EqlShellFields8120 as EqlShellFieldsLatest, + NewTermsFields8120 as NewTermsFieldsLatest, }; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts index bfbba49bb80ea..44d3023739446 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts @@ -107,3 +107,6 @@ export const alert_tags = t.type({ }); export type AlertTags = t.TypeOf; + +export const user_search_term = t.string; +export type UserSearchTerm = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/users/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/users/index.ts new file mode 100644 index 0000000000000..b4775b77bf69f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/users/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './suggest_user_profiles_route.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts new file mode 100644 index 0000000000000..f403501c52ea7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts @@ -0,0 +1,22 @@ +/* + * 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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type SuggestUserProfilesRequestQuery = z.infer; +export const SuggestUserProfilesRequestQuery = z.object({ + /** + * Query string used to match name-related fields in user profiles. The following fields are treated as name-related: username, full_name and email + */ + searchTerm: z.string().optional(), +}); +export type SuggestUserProfilesRequestQueryInput = z.input; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml new file mode 100644 index 0000000000000..babaedf1486ff --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.0 +info: + title: Suggest user profiles API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/_find: + summary: Suggests user profiles based on provided search term + post: + operationId: SuggestUserProfiles + x-codegen-enabled: true + description: Suggests user profiles. + parameters: + - name: searchTerm + in: query + required: false + description: "Query string used to match name-related fields in user profiles. The following fields are treated as name-related: username, full_name and email" + schema: + type: string + responses: + 200: + description: Indicates a successful call. + 400: + description: Invalid request. diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts new file mode 100644 index 0000000000000..378aaa3098584 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type IdField = z.infer; +export const IdField = z.enum(['host.name', 'user.name']); +export type IdFieldEnum = typeof IdField.enum; +export const IdFieldEnum = IdField.enum; + +export type AssetCriticalityRecordIdParts = z.infer; +export const AssetCriticalityRecordIdParts = z.object({ + /** + * The ID value of the asset. + */ + id_value: z.string(), + /** + * The field representing the ID. + */ + id_field: IdField, +}); + +export type CreateAssetCriticalityRecord = z.infer; +export const CreateAssetCriticalityRecord = AssetCriticalityRecordIdParts.merge( + z.object({ + /** + * The criticality level of the asset. + */ + criticality_level: z.enum(['very_important', 'important', 'normal', 'not_important']), + }) +); + +export type AssetCriticalityRecord = z.infer; +export const AssetCriticalityRecord = CreateAssetCriticalityRecord.merge( + z.object({ + /** + * The time the record was created or updated. + */ + '@timestamp': z.string().datetime(), + }) +); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml new file mode 100644 index 0000000000000..3271f990931ef --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml @@ -0,0 +1,66 @@ +openapi: 3.0.0 +info: + title: Asset Criticality Common Schema + description: Common schema for asset criticality + version: 1.0.0 +paths: { } +components: + parameters: + id_value: + name: id_value + in: query + required: true + schema: + type: string + description: The ID value of the asset. + id_field: + name: id_field + in: query + required: true + schema: + $ref: '#/components/schemas/IdField' + example: 'host.name' + description: The field representing the ID. + + schemas: + IdField: + type: string + enum: + - 'host.name' + - 'user.name' + AssetCriticalityRecordIdParts: + type: object + properties: + id_value: + type: string + description: The ID value of the asset. + id_field: + $ref: '#/components/schemas/IdField' + example: 'host.name' + description: The field representing the ID. + required: + - id_value + - id_field + CreateAssetCriticalityRecord: + allOf: + - $ref: '#/components/schemas/AssetCriticalityRecordIdParts' + - type: object + properties: + criticality_level: + type: string + enum: [very_important, important, normal, not_important] + description: The criticality level of the asset. + required: + - criticality_level + AssetCriticalityRecord: + allOf: + - $ref: '#/components/schemas/CreateAssetCriticalityRecord' + - type: object + properties: + "@timestamp": + type: string + format: 'date-time' + example: '2017-07-21T17:32:28Z' + description: The time the record was created or updated. + required: + - "@timestamp" diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml new file mode 100644 index 0000000000000..198fe9a35339b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Asset Criticality Create Record Schema +paths: + /internal/asset_criticality: + post: + summary: Create Criticality Record + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateAssetCriticalityRecord' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/SingleAssetCriticality' + '400': + description: Invalid request \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml new file mode 100644 index 0000000000000..e8334c48205f4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml @@ -0,0 +1,16 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Asset Criticality Delete Record Schema +paths: + /internal/asset_criticality: + delete: + summary: Delete Criticality Record + parameters: + - $ref: '#/components/parameters/id_value' + - $ref: '#/components/parameters/id_field' + responses: + '200': + description: Successful response + '400': + description: Invalid request \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml new file mode 100644 index 0000000000000..2aa88dcbc862c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml @@ -0,0 +1,22 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Asset Criticality Get Record Schema +paths: + /internal/asset_criticality: + get: + summary: Get Criticality Record + parameters: + - $ref: '#/components/parameters/id_value' + - $ref: '#/components/parameters/id_field' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/SingleAssetCriticality' + '400': + description: Invalid request + '404': + description: Criticality record not found \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.gen.ts new file mode 100644 index 0000000000000..6e034ae654f6e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.gen.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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type AssetCriticalityStatusResponse = z.infer; +export const AssetCriticalityStatusResponse = z.object({ + asset_criticality_resources_installed: z.boolean().optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/asset_criticality/status.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml similarity index 92% rename from x-pack/plugins/security_solution/common/api/asset_criticality/status.yaml rename to x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml index 7bdc5fd562c36..976833fae0706 100644 --- a/x-pack/plugins/security_solution/common/api/asset_criticality/status.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml @@ -15,7 +15,6 @@ paths: $ref: '#/components/schemas/AssetCriticalityStatusResponse' '400': description: Invalid request - responses: components: schemas: @@ -23,4 +22,4 @@ components: type: object properties: asset_criticality_resources_installed: - type: boolean \ No newline at end of file + type: boolean diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts new file mode 100644 index 0000000000000..d908c931ad1dd --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './common.gen'; +export * from './get_asset_criticality_status.gen'; diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/calculation_route_schema.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/calculation_route_schema.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/calculation_route_schema.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/calculation_route_schema.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/common.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/common.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/common.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/common.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_disable_route_schema.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_disable_route_schema.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/engine_disable_route_schema.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_disable_route_schema.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_enable_route_schema.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_enable_route_schema.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/engine_enable_route_schema.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_enable_route_schema.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_init_route_schema.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_init_route_schema.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/engine_init_route_schema.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_init_route_schema.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_status_route_schema.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_status_route_schema.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/engine_status_route_schema.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/engine_status_route_schema.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/preview_route_schema.yml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/preview_route_schema.yml similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_engine/preview_route_schema.yml rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/preview_route_schema.yml diff --git a/x-pack/plugins/security_solution/common/api/risk_score/create_index/create_index_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_index/create_index_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/create_index/create_index_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_index/create_index_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.test.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.test.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.test.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.test.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_prebuilt_saved_objects/create_prebuilt_saved_objects_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/create_stored_script/create_stored_script_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_stored_script/create_stored_script_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/create_stored_script/create_stored_script_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/create_stored_script/create_stored_script_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/delete_indices/delete_indices_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/delete_indices/delete_indices_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/delete_indices/delete_indices_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/delete_indices/delete_indices_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/delete_prebuilt_saved_objects/delete_prebuilt_saved_objects_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/delete_prebuilt_saved_objects/delete_prebuilt_saved_objects_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/delete_prebuilt_saved_objects/delete_prebuilt_saved_objects_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/delete_prebuilt_saved_objects/delete_prebuilt_saved_objects_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/delete_stored_script/delete_stored_script_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/delete_stored_script/delete_stored_script_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/delete_stored_script/delete_stored_script_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/delete_stored_script/delete_stored_script_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/index.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/index.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/index.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/index_status/index_status_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/index_status/index_status_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/index_status/index_status_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/index_status/index_status_route.ts diff --git a/x-pack/plugins/security_solution/common/api/risk_score/install_modules/install_modules_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/install_modules/install_modules_route.ts similarity index 89% rename from x-pack/plugins/security_solution/common/api/risk_score/install_modules/install_modules_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/install_modules/install_modules_route.ts index a17f10d724863..3f721f1c859d8 100644 --- a/x-pack/plugins/security_solution/common/api/risk_score/install_modules/install_modules_route.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/install_modules/install_modules_route.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { RiskScoreEntity } from '../../../search_strategy'; +import { RiskScoreEntity } from '../../../../search_strategy'; export const onboardingRiskScoreRequestBody = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/risk_score/read_prebuilt_dev_tool_content/read_prebuilt_dev_tool_content_route.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/read_prebuilt_dev_tool_content/read_prebuilt_dev_tool_content_route.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/risk_score/read_prebuilt_dev_tool_content/read_prebuilt_dev_tool_content_route.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/risk_score/read_prebuilt_dev_tool_content/read_prebuilt_dev_tool_content_route.ts diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e50533f223928..655df5eec753c 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -319,6 +319,10 @@ export const DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL = export const DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL = `${DETECTION_ENGINE_SIGNALS_URL}/finalize_migration` as const; export const DETECTION_ENGINE_ALERT_TAGS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/tags` as const; +export const DETECTION_ENGINE_ALERT_ASSIGNEES_URL = + `${DETECTION_ENGINE_SIGNALS_URL}/assignees` as const; +export const DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL = + `${DETECTION_ENGINE_SIGNALS_URL}/_find` as const; export const ALERTS_AS_DATA_URL = '/internal/rac/alerts' as const; export const ALERTS_AS_DATA_FIND_URL = `${ALERTS_AS_DATA_URL}/find` as const; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 81c4bd8406408..689ad118c9ca9 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -125,7 +125,7 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the risk engine privileges route * and associated callout in the UI */ - riskEnginePrivilegesRouteEnabled: false, + riskEnginePrivilegesRouteEnabled: true, /* * Enables experimental Entity Analytics Asset Criticality feature diff --git a/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts b/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts index 134b659116ee0..8435e6ec89845 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts @@ -8,6 +8,7 @@ import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { ColumnHeaderOptions, RowRenderer } from '../..'; +import type { RenderCellValueContext } from '../../../../public/detections/configurations/security_solution_detections/fetch_page_context'; import type { BrowserFields, TimelineNonEcsData } from '../../../search_strategy'; /** The following props are provided to the function called by `renderCellValue` */ @@ -28,4 +29,5 @@ export type CellValueElementProps = EuiDataGridCellValueElementProps & { truncate?: boolean; key?: string; closeCellPopover?: () => void; + context?: RenderCellValueContext; }; diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/alerts/user_assignment.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/alerts/user_assignment.md new file mode 100644 index 0000000000000..a2b360423e5c8 --- /dev/null +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/alerts/user_assignment.md @@ -0,0 +1,216 @@ +# Alert User Assignment + +This is a test plan for the Alert User Assignment feature + +Status: `in progress`. The current test plan covers functionality described in [Alert User Assignment](https://github.com/elastic/security-team/issues/2504) epic. + +## Useful information + +### Tickets + +- [Alert User Assignment](https://github.com/elastic/security-team/issues/2504) epic +- [Add test coverage for Alert User Assignment](https://github.com/elastic/kibana/issues/171307) +- [Write a test plan for Alert User Assignment](https://github.com/elastic/kibana/issues/171306) + +### Terminology + +- **Assignee**: The user assigned to an alert. + +- **Assignees field**: The alert's `kibana.alert.workflow_assignee_ids` field which contains an array of assignees IDs. These ids conrespond to [User Profiles](https://www.elastic.co/guide/en/elasticsearch/reference/current/user-profile.html) endpoint. + +- **Assignee's avatar**: The avatar of an assignee. Can be either user profile picture if uploaded by the user or initials of the user. + +- **Assignees count badge**: The badge with the number of assignees. + +### Assumptions + +- The feature is **NOT** available under the Basic license +- Assignees are stored as an array of users IDs in alert's `kibana.alert.workflow_assignee_ids` field +- There are multiple (five or more) available users which could be assigned to alerts +- User need to have editor or higher privileges to assign users to alerts +- Mixed states are not supported by the current version of User Profiles component +- "Displayed/Shown in UI" refers to "Alerts Table" and "Alert's Details Flyout" + +## Scenarios + +### Basic rendering + +#### **Scenario: No assignees** + +**Automation**: 2 e2e test + 2 unit test. + +```Gherkin +Given an alert doesn't have assignees +Then no assignees' (represented by avatars) should be displayed in UI +``` + +#### **Scenario: With assignees** + +**Automation**: 2 e2e test + 2 unit test. + +```Gherkin +Given an alert has assignees +Then assignees' (represented by avatars) for each assignee should be shown in UI +``` + +#### **Scenario: Many assignees (Badge)** + +**Automation**: 2 e2e test + 2 unit test. + +```Gherkin +Given an alert has more assignees than maximum number allowed to display +Then assignees count badge is displayed in UI +``` + +### Updating assignees (single alert) + +#### **Scenario: Add new assignees** + +**Automation**: 3 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given an alert +When user adds new assignees +Then assignees field should be updated +And newly added assignees should be present +``` + +#### **Scenario: Update assignees** + +**Automation**: 3 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given an alert with assignees +When user removes some of (or all) current assignees and adds new assignees +Then assignees field should be updated +And removed assignees should be absent +And newly added assignees should be present +``` + +#### **Scenario: Unassign alert** + +**Automation**: 2 e2e test + 1 unit test. + +```Gherkin +Given an alert with assignees +When user triggers "Unassign alert" action +Then assignees field should be updated +And assignees field should be empty +``` + +### Updating assignees (bulk actions) + +#### **Scenario: Add new assignees** + +**Automation**: 1 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given multiple alerts +When user adds new assignees +Then assignees fields of all involved alerts should be updated +And newly added assignees should be present +``` + +#### **Scenario: Update assignees** + +**Automation**: 1 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given multiple alerts with assignees +When user removes some of (or all) current assignees and adds new assignees +Then assignees fields of all involved alerts should be updated +And removed assignees should be absent +And newly added assignees should be present +``` + +#### **Scenario: Unassign alert** + +**Automation**: 1 e2e test + 1 unit test. + +```Gherkin +Given multiple alerts with assignees +When user triggers "Unassign alert" action +Then assignees fields of all involved alerts should be updated +And assignees fields should be empty +``` + +### Alerts filtering + +#### **Scenario: By one assignee** + +**Automation**: 1 e2e test + 1 unit test. + +```Gherkin +Given multiple alerts with and without assignees +When user filters by one of the assignees +Then only alerts with selected assignee in assignees field are displayed +``` + +#### **Scenario: By multiple assignees** + +**Automation**: 1 e2e test + 1 unit test. + +```Gherkin +Given multiple alerts with and without assignees +When user filters by multiple assignees +Then all alerts with either of selected assignees in assignees fields are displayed +``` + +#### **Scenario: "No assignees" option** + +**Automation**: 1 e2e test + 1 unit test. + +```Gherkin +Given filter by assignees UI is available +Then there should be an option to filter alerts to see those which are not assigned to anyone +``` + +#### **Scenario: By "No assignees"** + +**Automation**: 1 e2e test + 1 unit test. + +```Gherkin +Given multiple alerts with and without assignees +When user filters by "No assignees" option +Then all alerts with empty assignees fields are displayed +``` + +#### **Scenario: By assignee and alert status** + +**Automation**: 1 e2e test + 1 unit test. + +```Gherkin +Given multiple alerts with and without assignees +When user filters by one of the assignees +AND alert's status +Then only alerts with selected assignee in assignees field AND selected alert's status are displayed +``` + +### Authorization / RBAC + +#### **Scenario: Viewer role** + +**Automation**: 1 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given user has "viewer/readonly" role +Then there should not be a way to update assignees field for an alert +``` + +#### **Scenario: Serverless roles** + +**Automation**: 1 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given users 't1_analyst', 't2_analyst', 't3_analyst', 'rule_author', 'soc_manager', 'detections_admin', 'platform_engineer' roles +Then update assignees functionality should be available +``` + +#### **Scenario: Basic license** + +**Automation**: 1 e2e test + 1 unit test + 1 integration test. + +```Gherkin +Given user runs Kibana under the Basic license +Then update assignees functionality should not be available +``` diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/assignees_apply_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_apply_panel.test.tsx new file mode 100644 index 0000000000000..e9054a6817e14 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_apply_panel.test.tsx @@ -0,0 +1,139 @@ +/* + * 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 { render } from '@testing-library/react'; + +import { ASSIGNEES_APPLY_BUTTON_TEST_ID, ASSIGNEES_APPLY_PANEL_TEST_ID } from './test_ids'; +import { AssigneesApplyPanel } from './assignees_apply_panel'; + +import { useGetCurrentUserProfile } from '../user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../user_profiles/use_suggest_users'; +import { TestProviders } from '../../mock'; +import * as i18n from './translations'; +import { mockUserProfiles } from './mocks'; + +jest.mock('../user_profiles/use_get_current_user_profile'); +jest.mock('../user_profiles/use_bulk_get_user_profiles'); +jest.mock('../user_profiles/use_suggest_users'); + +const renderAssigneesApplyPanel = ( + { + assignedUserIds, + showUnassignedOption, + onSelectionChange, + onAssigneesApply, + }: { + assignedUserIds: string[]; + showUnassignedOption?: boolean; + onSelectionChange?: () => void; + onAssigneesApply?: () => void; + } = { assignedUserIds: [] } +) => { + const assignedProfiles = mockUserProfiles.filter((user) => assignedUserIds.includes(user.uid)); + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: assignedProfiles, + }); + return render( + + + + ); +}; + +describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], + }); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + }); + + it('should render component', () => { + const { getByTestId, queryByTestId } = renderAssigneesApplyPanel(); + + expect(getByTestId(ASSIGNEES_APPLY_PANEL_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render apply button if `onAssigneesApply` callback provided', () => { + const { getByTestId } = renderAssigneesApplyPanel({ + assignedUserIds: [], + onAssigneesApply: jest.fn(), + }); + + expect(getByTestId(ASSIGNEES_APPLY_PANEL_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID)).toBeInTheDocument(); + }); + + it('should render `no assignees` option', () => { + const { getByTestId } = renderAssigneesApplyPanel({ + assignedUserIds: [], + showUnassignedOption: true, + onAssigneesApply: jest.fn(), + }); + + const assigneesList = getByTestId('euiSelectableList'); + expect(assigneesList).toHaveTextContent(i18n.ASSIGNEES_NO_ASSIGNEES); + }); + + it('should call `onAssigneesApply` on apply button click', () => { + const onAssigneesApplyMock = jest.fn(); + const { getByText, getByTestId } = renderAssigneesApplyPanel({ + assignedUserIds: ['user-id-1'], + onAssigneesApply: onAssigneesApplyMock, + }); + + getByText(mockUserProfiles[1].user.full_name).click(); + getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID).click(); + + expect(onAssigneesApplyMock).toHaveBeenCalledTimes(1); + expect(onAssigneesApplyMock).toHaveBeenLastCalledWith(['user-id-2', 'user-id-1']); + }); + + it('should call `onSelectionChange` on user selection', () => { + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: [], + }); + + const onSelectionChangeMock = jest.fn(); + const { getByText } = renderAssigneesApplyPanel({ + assignedUserIds: [], + onSelectionChange: onSelectionChangeMock, + }); + + getByText('User 1').click(); + getByText('User 2').click(); + getByText('User 3').click(); + getByText('User 3').click(); + getByText('User 2').click(); + getByText('User 1').click(); + + expect(onSelectionChangeMock).toHaveBeenCalledTimes(6); + expect(onSelectionChangeMock.mock.calls).toEqual([ + [['user-id-1']], + [['user-id-2', 'user-id-1']], + [['user-id-3', 'user-id-2', 'user-id-1']], + [['user-id-2', 'user-id-1']], + [['user-id-1']], + [[]], + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/assignees_apply_panel.tsx b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_apply_panel.tsx new file mode 100644 index 0000000000000..a263b660b7536 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_apply_panel.tsx @@ -0,0 +1,156 @@ +/* + * 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 { isEqual } from 'lodash/fp'; +import type { FC } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; + +import { EuiButton } from '@elastic/eui'; +import { UserProfilesSelectable } from '@kbn/user-profile-components'; + +import { isEmpty } from 'lodash'; +import { useGetCurrentUserProfile } from '../user_profiles/use_get_current_user_profile'; +import * as i18n from './translations'; +import type { AssigneesIdsSelection, AssigneesProfilesSelection } from './types'; +import { NO_ASSIGNEES_VALUE } from './constants'; +import { useSuggestUsers } from '../user_profiles/use_suggest_users'; +import { useBulkGetUserProfiles } from '../user_profiles/use_bulk_get_user_profiles'; +import { bringCurrentUserToFrontAndSort, removeNoAssigneesSelection } from './utils'; +import { ASSIGNEES_APPLY_BUTTON_TEST_ID, ASSIGNEES_APPLY_PANEL_TEST_ID } from './test_ids'; + +export interface AssigneesApplyPanelProps { + /** + * Identifier of search field. + */ + searchInputId?: string; + + /** + * Ids of the users assigned to the alert + */ + assignedUserIds: AssigneesIdsSelection[]; + + /** + * Show "Unassigned" option if needed + */ + showUnassignedOption?: boolean; + + /** + * Callback to handle changing of the assignees selection + */ + onSelectionChange?: (users: AssigneesIdsSelection[]) => void; + + /** + * Callback to handle applying assignees. If provided will show "Apply assignees" button + */ + onAssigneesApply?: (selectedAssignees: AssigneesIdsSelection[]) => void; +} + +/** + * The popover to allow selection of users from a list + */ +export const AssigneesApplyPanel: FC = memo( + ({ + searchInputId, + assignedUserIds, + showUnassignedOption, + onSelectionChange, + onAssigneesApply, + }) => { + const { data: currentUserProfile } = useGetCurrentUserProfile(); + const existingIds = useMemo( + () => new Set(removeNoAssigneesSelection(assignedUserIds)), + [assignedUserIds] + ); + const { isLoading: isLoadingAssignedUsers, data: assignedUsers } = useBulkGetUserProfiles({ + uids: existingIds, + }); + + const [searchTerm, setSearchTerm] = useState(''); + const { isLoading: isLoadingSuggestedUsers, data: userProfiles } = useSuggestUsers({ + searchTerm, + }); + + const searchResultProfiles = useMemo(() => { + const sortedUsers = bringCurrentUserToFrontAndSort(currentUserProfile, userProfiles) ?? []; + + if (showUnassignedOption && isEmpty(searchTerm)) { + return [NO_ASSIGNEES_VALUE, ...sortedUsers]; + } + + return sortedUsers; + }, [currentUserProfile, searchTerm, showUnassignedOption, userProfiles]); + + const [selectedAssignees, setSelectedAssignees] = useState([]); + useEffect(() => { + if (isLoadingAssignedUsers || !assignedUsers) { + return; + } + const hasNoAssigneesSelection = assignedUserIds.find((uid) => uid === NO_ASSIGNEES_VALUE); + const newAssignees = + hasNoAssigneesSelection !== undefined + ? [NO_ASSIGNEES_VALUE, ...assignedUsers] + : assignedUsers; + setSelectedAssignees(newAssignees); + }, [assignedUserIds, assignedUsers, isLoadingAssignedUsers]); + + const handleSelectedAssignees = useCallback( + (newAssignees: AssigneesProfilesSelection[]) => { + if (!isEqual(newAssignees, selectedAssignees)) { + setSelectedAssignees(newAssignees); + onSelectionChange?.(newAssignees.map((assignee) => assignee?.uid ?? NO_ASSIGNEES_VALUE)); + } + }, + [onSelectionChange, selectedAssignees] + ); + + const handleApplyButtonClick = useCallback(() => { + const selectedIds = selectedAssignees.map((assignee) => assignee?.uid ?? NO_ASSIGNEES_VALUE); + onAssigneesApply?.(selectedIds); + }, [onAssigneesApply, selectedAssignees]); + + const selectedStatusMessage = useCallback( + (total: number) => i18n.ASSIGNEES_SELECTION_STATUS_MESSAGE(total), + [] + ); + + const isLoading = isLoadingAssignedUsers || isLoadingSuggestedUsers; + + return ( +
+ { + setSearchTerm(term); + }} + onChange={handleSelectedAssignees} + selectedStatusMessage={selectedStatusMessage} + options={searchResultProfiles} + selectedOptions={selectedAssignees} + isLoading={isLoading} + height={'full'} + singleSelection={false} + searchPlaceholder={i18n.ASSIGNEES_SEARCH_USERS} + clearButtonLabel={i18n.ASSIGNEES_CLEAR_FILTERS} + nullOptionLabel={i18n.ASSIGNEES_NO_ASSIGNEES} + /> + {onAssigneesApply && ( + + {i18n.ASSIGNEES_APPLY_BUTTON} + + )} +
+ ); + } +); + +AssigneesApplyPanel.displayName = 'AssigneesPanel'; diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/assignees_popover.test.tsx b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_popover.test.tsx new file mode 100644 index 0000000000000..d26cb35c1fc9e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_popover.test.tsx @@ -0,0 +1,98 @@ +/* + * 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 { render } from '@testing-library/react'; + +import { ASSIGNEES_APPLY_PANEL_TEST_ID } from './test_ids'; +import { AssigneesPopover } from './assignees_popover'; + +import { useGetCurrentUserProfile } from '../user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../user_profiles/use_suggest_users'; +import { TestProviders } from '../../mock'; +import { mockUserProfiles } from './mocks'; +import { EuiButton } from '@elastic/eui'; + +jest.mock('../user_profiles/use_get_current_user_profile'); +jest.mock('../user_profiles/use_bulk_get_user_profiles'); +jest.mock('../user_profiles/use_suggest_users'); + +const MOCK_BUTTON_TEST_ID = 'mock-assignees-button'; + +const renderAssigneesPopover = ({ + assignedUserIds, + isPopoverOpen, +}: { + assignedUserIds: string[]; + isPopoverOpen: boolean; +}) => { + const assignedProfiles = mockUserProfiles.filter((user) => assignedUserIds.includes(user.uid)); + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: assignedProfiles, + }); + return render( + + } + isPopoverOpen={isPopoverOpen} + closePopover={jest.fn()} + /> + + ); +}; + +describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], + }); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + }); + + it('should render closed popover component', () => { + const { getByTestId, queryByTestId } = renderAssigneesPopover({ + assignedUserIds: [], + isPopoverOpen: false, + }); + + expect(getByTestId(MOCK_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(ASSIGNEES_APPLY_PANEL_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render opened popover component', () => { + const { getByTestId } = renderAssigneesPopover({ + assignedUserIds: [], + isPopoverOpen: true, + }); + + expect(getByTestId(MOCK_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_APPLY_PANEL_TEST_ID)).toBeInTheDocument(); + }); + + it('should render assignees', () => { + const { getByTestId } = renderAssigneesPopover({ + assignedUserIds: [], + isPopoverOpen: true, + }); + + const assigneesList = getByTestId('euiSelectableList'); + expect(assigneesList).toHaveTextContent('User 1'); + expect(assigneesList).toHaveTextContent('user1@test.com'); + expect(assigneesList).toHaveTextContent('User 2'); + expect(assigneesList).toHaveTextContent('user2@test.com'); + expect(assigneesList).toHaveTextContent('User 3'); + expect(assigneesList).toHaveTextContent('user3@test.com'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/assignees_popover.tsx b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_popover.tsx new file mode 100644 index 0000000000000..b392855aaf6f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/assignees_popover.tsx @@ -0,0 +1,94 @@ +/* + * 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 { FC, ReactNode } from 'react'; +import React, { memo } from 'react'; + +import { EuiPopover, useGeneratedHtmlId } from '@elastic/eui'; + +import { ASSIGNEES_PANEL_WIDTH } from './constants'; +import { AssigneesApplyPanel } from './assignees_apply_panel'; +import type { AssigneesIdsSelection } from './types'; + +export interface AssigneesPopoverProps { + /** + * Ids of the users assigned to the alert + */ + assignedUserIds: AssigneesIdsSelection[]; + + /** + * Show "Unassigned" option if needed + */ + showUnassignedOption?: boolean; + + /** + * Triggering element for which to align the popover to + */ + button: NonNullable; + + /** + * Boolean to allow popover to be opened or closed + */ + isPopoverOpen: boolean; + + /** + * Callback to handle hiding of the popover + */ + closePopover: () => void; + + /** + * Callback to handle changing of the assignees selection + */ + onSelectionChange?: (users: AssigneesIdsSelection[]) => void; + + /** + * Callback to handle applying assignees + */ + onAssigneesApply?: (selectedAssignees: AssigneesIdsSelection[]) => void; +} + +/** + * The popover to allow selection of users from a list + */ +export const AssigneesPopover: FC = memo( + ({ + assignedUserIds, + showUnassignedOption, + button, + isPopoverOpen, + closePopover, + onSelectionChange, + onAssigneesApply, + }) => { + const searchInputId = useGeneratedHtmlId({ + prefix: 'searchInput', + }); + + return ( + + + + ); + } +); + +AssigneesPopover.displayName = 'AssigneesPopover'; diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/constants.ts b/x-pack/plugins/security_solution/public/common/components/assignees/constants.ts new file mode 100644 index 0000000000000..fe12bff429ea8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/constants.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export const ASSIGNEES_PANEL_WIDTH = 400; + +export const NO_ASSIGNEES_VALUE = null; diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/mocks.ts b/x-pack/plugins/security_solution/public/common/components/assignees/mocks.ts new file mode 100644 index 0000000000000..a3e578eb4ae30 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/mocks.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +export const mockUserProfiles = [ + { + uid: 'user-id-1', + enabled: true, + user: { username: 'user1', full_name: 'User 1', email: 'user1@test.com' }, + data: {}, + }, + { + uid: 'user-id-2', + enabled: true, + user: { username: 'user2', full_name: 'User 2', email: 'user2@test.com' }, + data: {}, + }, + { + uid: 'user-id-3', + enabled: true, + user: { username: 'user3', full_name: 'User 3', email: 'user3@test.com' }, + data: {}, + }, +]; diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/test_ids.ts b/x-pack/plugins/security_solution/public/common/components/assignees/test_ids.ts new file mode 100644 index 0000000000000..0842e8b5f3e98 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/test_ids.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const PREFIX = 'securitySolutionAssignees'; + +/* Apply Panel */ +export const ASSIGNEES_APPLY_PANEL_TEST_ID = `${PREFIX}ApplyPanel`; +export const ASSIGNEES_APPLY_BUTTON_TEST_ID = `${PREFIX}ApplyButton`; diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/translations.ts b/x-pack/plugins/security_solution/public/common/components/assignees/translations.ts new file mode 100644 index 0000000000000..fdd22f50aa7cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/translations.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 { i18n } from '@kbn/i18n'; + +export const ASSIGNEES_SELECTION_STATUS_MESSAGE = (total: number) => + i18n.translate('xpack.securitySolution.assignees.totalUsersAssigned', { + defaultMessage: '{total, plural, one {# filter} other {# filters}} selected', + values: { total }, + }); + +export const ASSIGNEES_APPLY_BUTTON = i18n.translate( + 'xpack.securitySolution.assignees.applyButtonTitle', + { + defaultMessage: 'Apply', + } +); + +export const ASSIGNEES_SEARCH_USERS = i18n.translate( + 'xpack.securitySolution.assignees.selectableSearchPlaceholder', + { + defaultMessage: 'Search users', + } +); + +export const ASSIGNEES_CLEAR_FILTERS = i18n.translate( + 'xpack.securitySolution.assignees.clearFilters', + { + defaultMessage: 'Clear filters', + } +); + +export const ASSIGNEES_NO_ASSIGNEES = i18n.translate( + 'xpack.securitySolution.assignees.noAssigneesLabel', + { + defaultMessage: 'No assignees', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/types.ts b/x-pack/plugins/security_solution/public/common/components/assignees/types.ts new file mode 100644 index 0000000000000..3ee7b04dc23a2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; + +export type AssigneesIdsSelection = string | null; +export type AssigneesProfilesSelection = UserProfileWithAvatar | null; diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/utils.test.tsx b/x-pack/plugins/security_solution/public/common/components/assignees/utils.test.tsx new file mode 100644 index 0000000000000..0b75e90a91b3f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/utils.test.tsx @@ -0,0 +1,44 @@ +/* + * 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 { NO_ASSIGNEES_VALUE } from './constants'; +import { mockUserProfiles } from './mocks'; +import { bringCurrentUserToFrontAndSort, removeNoAssigneesSelection } from './utils'; + +describe('utils', () => { + describe('removeNoAssigneesSelection', () => { + it('should return user ids if `no assignees` has not been passed', () => { + const assignees = ['user1', 'user2', 'user3']; + const ids = removeNoAssigneesSelection(assignees); + expect(ids).toEqual(assignees); + }); + + it('should return user ids and remove `no assignees`', () => { + const assignees = [NO_ASSIGNEES_VALUE, 'user1', 'user2', NO_ASSIGNEES_VALUE, 'user3']; + const ids = removeNoAssigneesSelection(assignees); + expect(ids).toEqual(['user1', 'user2', 'user3']); + }); + }); + + describe('bringCurrentUserToFrontAndSort', () => { + it('should return `undefined` if nothing has been passed', () => { + const sortedProfiles = bringCurrentUserToFrontAndSort(); + expect(sortedProfiles).toBeUndefined(); + }); + + it('should return passed profiles if current user is `undefined`', () => { + const sortedProfiles = bringCurrentUserToFrontAndSort(undefined, mockUserProfiles); + expect(sortedProfiles).toEqual(mockUserProfiles); + }); + + it('should return profiles with the current user on top', () => { + const currentUser = mockUserProfiles[1]; + const sortedProfiles = bringCurrentUserToFrontAndSort(currentUser, mockUserProfiles); + expect(sortedProfiles).toEqual([currentUser, mockUserProfiles[0], mockUserProfiles[2]]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/assignees/utils.ts b/x-pack/plugins/security_solution/public/common/components/assignees/utils.ts new file mode 100644 index 0000000000000..9eae9503febd0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/assignees/utils.ts @@ -0,0 +1,59 @@ +/* + * 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 { sortBy } from 'lodash'; + +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; + +import { NO_ASSIGNEES_VALUE } from './constants'; +import type { AssigneesIdsSelection } from './types'; + +const getSortField = (profile: UserProfileWithAvatar) => + profile.user?.full_name?.toLowerCase() ?? + profile.user?.email?.toLowerCase() ?? + profile.user?.username.toLowerCase(); + +const sortProfiles = (profiles?: UserProfileWithAvatar[]) => { + if (!profiles) { + return; + } + + return sortBy(profiles, getSortField); +}; + +const moveCurrentUserToBeginning = ( + currentUserProfile?: T, + profiles?: T[] +) => { + if (!profiles) { + return; + } + + if (!currentUserProfile) { + return profiles; + } + + const currentProfileIndex = profiles.find((profile) => profile.uid === currentUserProfile.uid); + + if (!currentProfileIndex) { + return profiles; + } + + const profilesWithoutCurrentUser = profiles.filter( + (profile) => profile.uid !== currentUserProfile.uid + ); + + return [currentUserProfile, ...profilesWithoutCurrentUser]; +}; + +export const bringCurrentUserToFrontAndSort = ( + currentUserProfile?: UserProfileWithAvatar, + profiles?: UserProfileWithAvatar[] +) => moveCurrentUserToBeginning(currentUserProfile, sortProfiles(profiles)); + +export const removeNoAssigneesSelection = (assignees: AssigneesIdsSelection[]): string[] => + assignees.filter((assignee): assignee is string => assignee !== NO_ASSIGNEES_VALUE); diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/constants.ts b/x-pack/plugins/security_solution/public/common/components/filter_group/constants.ts index 873355fa60a76..9eef5311b278b 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/constants.ts @@ -24,6 +24,7 @@ export const TEST_IDS = { EDIT: 'filter-group__context--edit', DISCARD: `filter-group__context--discard`, }, + FILTER_BY_ASSIGNEES_BUTTON: 'filter-popover-button-assignees', }; export const COMMON_OPTIONS_LIST_CONTROL_INPUTS: Partial = { diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/filter_by_assignees.test.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_by_assignees.test.tsx new file mode 100644 index 0000000000000..872d6f8e901a4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_by_assignees.test.tsx @@ -0,0 +1,132 @@ +/* + * 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 { render } from '@testing-library/react'; + +import { FilterByAssigneesPopover } from './filter_by_assignees'; +import { TEST_IDS } from './constants'; +import { TestProviders } from '../../mock'; +import type { AssigneesIdsSelection } from '../assignees/types'; + +import { useGetCurrentUserProfile } from '../user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../user_profiles/use_suggest_users'; +import { useLicense } from '../../hooks/use_license'; +import { useUpsellingMessage } from '../../hooks/use_upselling'; + +jest.mock('../user_profiles/use_get_current_user_profile'); +jest.mock('../user_profiles/use_bulk_get_user_profiles'); +jest.mock('../user_profiles/use_suggest_users'); +jest.mock('../../hooks/use_license'); +jest.mock('../../hooks/use_upselling'); + +const mockUserProfiles = [ + { + uid: 'user-id-1', + enabled: true, + user: { username: 'user1', full_name: 'User 1', email: 'user1@test.com' }, + data: {}, + }, + { + uid: 'user-id-2', + enabled: true, + user: { username: 'user2', full_name: 'User 2', email: 'user2@test.com' }, + data: {}, + }, + { + uid: 'user-id-3', + enabled: true, + user: { username: 'user3', full_name: 'User 3', email: 'user3@test.com' }, + data: {}, + }, +]; + +const renderFilterByAssigneesPopover = ( + alertAssignees: AssigneesIdsSelection[] = [], + onUsersChange = jest.fn() +) => + render( + + + + ); + +describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], + }); + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: [], + }); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => true }); + (useUpsellingMessage as jest.Mock).mockReturnValue('Go for Platinum!'); + }); + + it('should render closed popover component', () => { + const { getByTestId, queryByTestId } = renderFilterByAssigneesPopover(); + + expect(getByTestId(TEST_IDS.FILTER_BY_ASSIGNEES_BUTTON)).toBeInTheDocument(); + expect(queryByTestId('euiSelectableList')).not.toBeInTheDocument(); + }); + + it('should render opened popover component', () => { + const { getByTestId } = renderFilterByAssigneesPopover(); + + getByTestId(TEST_IDS.FILTER_BY_ASSIGNEES_BUTTON).click(); + expect(getByTestId('euiSelectableList')).toBeInTheDocument(); + }); + + it('should render assignees', () => { + const { getByTestId } = renderFilterByAssigneesPopover(); + + getByTestId(TEST_IDS.FILTER_BY_ASSIGNEES_BUTTON).click(); + + const assigneesList = getByTestId('euiSelectableList'); + expect(assigneesList).toHaveTextContent('User 1'); + expect(assigneesList).toHaveTextContent('user1@test.com'); + expect(assigneesList).toHaveTextContent('User 2'); + expect(assigneesList).toHaveTextContent('user2@test.com'); + expect(assigneesList).toHaveTextContent('User 3'); + expect(assigneesList).toHaveTextContent('user3@test.com'); + }); + + it('should call onUsersChange on closing the popover', () => { + const onUsersChangeMock = jest.fn(); + const { getByTestId, getByText } = renderFilterByAssigneesPopover([], onUsersChangeMock); + + getByTestId(TEST_IDS.FILTER_BY_ASSIGNEES_BUTTON).click(); + + getByText('User 1').click(); + getByText('User 2').click(); + getByText('User 3').click(); + getByText('User 3').click(); + getByText('User 2').click(); + getByText('User 1').click(); + + expect(onUsersChangeMock).toHaveBeenCalledTimes(6); + expect(onUsersChangeMock.mock.calls).toEqual([ + [['user-id-1']], + [['user-id-2', 'user-id-1']], + [['user-id-3', 'user-id-2', 'user-id-1']], + [['user-id-2', 'user-id-1']], + [['user-id-1']], + [[]], + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/filter_by_assignees.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_by_assignees.tsx new file mode 100644 index 0000000000000..fbef830dd1b85 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_by_assignees.tsx @@ -0,0 +1,93 @@ +/* + * 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 { FC } from 'react'; +import React, { memo, useCallback, useState } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { EuiFilterButton, EuiFilterGroup, EuiToolTip } from '@elastic/eui'; + +import { TEST_IDS } from './constants'; +import { AssigneesPopover } from '../assignees/assignees_popover'; +import type { AssigneesIdsSelection } from '../assignees/types'; +import { useLicense } from '../../hooks/use_license'; +import { useUpsellingMessage } from '../../hooks/use_upselling'; + +export interface FilterByAssigneesPopoverProps { + /** + * Ids of the users assigned to the alert + */ + assignedUserIds: AssigneesIdsSelection[]; + + /** + * Callback to handle changing of the assignees selection + */ + onSelectionChange?: (users: AssigneesIdsSelection[]) => void; +} + +/** + * The popover to filter alerts by assigned users + */ +export const FilterByAssigneesPopover: FC = memo( + ({ assignedUserIds, onSelectionChange }) => { + const isPlatinumPlus = useLicense().isPlatinumPlus(); + const upsellingMessage = useUpsellingMessage('alert_assignments'); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = useCallback(() => setIsPopoverOpen((value) => !value), []); + + const [selectedAssignees, setSelectedAssignees] = + useState(assignedUserIds); + const handleSelectionChange = useCallback( + (users: AssigneesIdsSelection[]) => { + setSelectedAssignees(users); + onSelectionChange?.(users); + }, + [onSelectionChange] + ); + + return ( + + + 0} + numActiveFilters={selectedAssignees.length} + > + {i18n.translate('xpack.securitySolution.filtersGroup.assignees.buttonTitle', { + defaultMessage: 'Assignees', + })} + + + } + isPopoverOpen={isPopoverOpen} + closePopover={togglePopover} + onSelectionChange={handleSelectionChange} + /> + + ); + } +); + +FilterByAssigneesPopover.displayName = 'FilterByAssigneesPopover'; diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.test.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.test.tsx new file mode 100644 index 0000000000000..abd3db47ea388 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.test.tsx @@ -0,0 +1,189 @@ +/* + * 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 { TimelineItem } from '@kbn/timelines-plugin/common'; +import { act, fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../mock'; +import { useGetCurrentUserProfile } from '../../user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../../user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../../user_profiles/use_suggest_users'; + +import { BulkAlertAssigneesPanel } from './alert_bulk_assignees'; +import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; +import { ASSIGNEES_APPLY_BUTTON_TEST_ID } from '../../assignees/test_ids'; + +jest.mock('../../user_profiles/use_get_current_user_profile'); +jest.mock('../../user_profiles/use_bulk_get_user_profiles'); +jest.mock('../../user_profiles/use_suggest_users'); + +const mockUserProfiles = [ + { uid: 'user-id-1', enabled: true, user: { username: 'user1' }, data: {} }, + { uid: 'user-id-2', enabled: true, user: { username: 'user2' }, data: {} }, +]; + +const mockSuggestedUserProfiles = [ + ...mockUserProfiles, + { uid: 'user-id-3', enabled: true, user: { username: 'user3' }, data: {} }, + { uid: 'user-id-4', enabled: true, user: { username: 'user4' }, data: {} }, +]; + +const mockAlertsWithAssignees = [ + { + _id: 'test-id', + data: [ + { + field: ALERT_WORKFLOW_ASSIGNEE_IDS, + value: ['user-id-1', 'user-id-2'], + }, + ], + ecs: { _id: 'test-id' }, + }, + { + _id: 'test-id', + data: [ + { + field: ALERT_WORKFLOW_ASSIGNEE_IDS, + value: ['user-id-1', 'user-id-2'], + }, + ], + ecs: { _id: 'test-id' }, + }, +]; + +(useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], +}); +(useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, +}); +(useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockSuggestedUserProfiles, +}); + +const renderAssigneesMenu = ( + items: TimelineItem[], + closePopover: () => void = jest.fn(), + onSubmit: () => Promise = jest.fn(), + setIsLoading: () => void = jest.fn() +) => { + return render( + + + + ); +}; + +describe('BulkAlertAssigneesPanel', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it renders', () => { + const wrapper = renderAssigneesMenu(mockAlertsWithAssignees); + + expect(wrapper.getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(useSuggestUsers).toHaveBeenCalled(); + }); + + test('it calls expected functions on submit when nothing has changed', () => { + const mockedClosePopover = jest.fn(); + const mockedOnSubmit = jest.fn(); + const mockedSetIsLoading = jest.fn(); + + const wrapper = renderAssigneesMenu( + mockAlertsWithAssignees, + mockedClosePopover, + mockedOnSubmit, + mockedSetIsLoading + ); + + act(() => { + fireEvent.click(wrapper.getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID)); + }); + expect(mockedClosePopover).toHaveBeenCalled(); + expect(mockedOnSubmit).not.toHaveBeenCalled(); + expect(mockedSetIsLoading).not.toHaveBeenCalled(); + }); + + test('it updates state correctly', () => { + const wrapper = renderAssigneesMenu(mockAlertsWithAssignees); + + const deselectUser = (userName: string, index: number) => { + expect(wrapper.getAllByRole('option')[index]).toHaveAttribute('title', userName); + expect(wrapper.getAllByRole('option')[index]).toBeChecked(); + act(() => { + fireEvent.click(wrapper.getByText(userName)); + }); + expect(wrapper.getAllByRole('option')[index]).toHaveAttribute('title', userName); + expect(wrapper.getAllByRole('option')[index]).not.toBeChecked(); + }; + + const selectUser = (userName: string, index = 0) => { + expect(wrapper.getAllByRole('option')[index]).toHaveAttribute('title', userName); + expect(wrapper.getAllByRole('option')[index]).not.toBeChecked(); + act(() => { + fireEvent.click(wrapper.getByText(userName)); + }); + expect(wrapper.getAllByRole('option')[index]).toHaveAttribute('title', userName); + expect(wrapper.getAllByRole('option')[index]).toBeChecked(); + }; + + deselectUser('user1', 0); + deselectUser('user2', 1); + selectUser('user3', 2); + selectUser('user4', 3); + }); + + test('it calls expected functions on submit when alerts have changed', () => { + const mockedClosePopover = jest.fn(); + const mockedOnSubmit = jest.fn(); + const mockedSetIsLoading = jest.fn(); + + const wrapper = renderAssigneesMenu( + mockAlertsWithAssignees, + mockedClosePopover, + mockedOnSubmit, + mockedSetIsLoading + ); + act(() => { + fireEvent.click(wrapper.getByText('user1')); + }); + act(() => { + fireEvent.click(wrapper.getByText('user2')); + }); + act(() => { + fireEvent.click(wrapper.getByText('user3')); + }); + act(() => { + fireEvent.click(wrapper.getByText('user4')); + }); + + act(() => { + fireEvent.click(wrapper.getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID)); + }); + expect(mockedClosePopover).toHaveBeenCalled(); + expect(mockedOnSubmit).toHaveBeenCalled(); + expect(mockedOnSubmit).toHaveBeenCalledWith( + { + add: ['user-id-4', 'user-id-3'], + remove: ['user-id-1', 'user-id-2'], + }, + ['test-id', 'test-id'], + expect.anything(), // An anonymous callback defined in the onSubmit function + mockedSetIsLoading + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.tsx new file mode 100644 index 0000000000000..9e312e6b366e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.tsx @@ -0,0 +1,82 @@ +/* + * 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 { intersection } from 'lodash'; +import React, { memo, useCallback, useMemo } from 'react'; + +import type { TimelineItem } from '@kbn/timelines-plugin/common'; +import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; + +import type { SetAlertAssigneesFunc } from './use_set_alert_assignees'; +import { AssigneesApplyPanel } from '../../assignees/assignees_apply_panel'; +import type { AssigneesIdsSelection } from '../../assignees/types'; +import { removeNoAssigneesSelection } from '../../assignees/utils'; + +interface BulkAlertAssigneesPanelComponentProps { + alertItems: TimelineItem[]; + setIsLoading: (isLoading: boolean) => void; + refresh?: () => void; + clearSelection?: () => void; + closePopoverMenu: () => void; + onSubmit: SetAlertAssigneesFunc; +} +const BulkAlertAssigneesPanelComponent: React.FC = ({ + alertItems, + refresh, + setIsLoading, + clearSelection, + closePopoverMenu, + onSubmit, +}) => { + const assignedUserIds = useMemo( + () => + intersection( + ...alertItems.map( + (item) => + item.data.find((data) => data.field === ALERT_WORKFLOW_ASSIGNEE_IDS)?.value ?? [] + ) + ), + [alertItems] + ); + + const onAssigneesApply = useCallback( + async (assigneesIds: AssigneesIdsSelection[]) => { + const updatedIds = removeNoAssigneesSelection(assigneesIds); + const assigneesToAddArray = updatedIds.filter((uid) => uid && !assignedUserIds.includes(uid)); + const assigneesToRemoveArray = assignedUserIds.filter( + (uid) => uid && !updatedIds.includes(uid) + ); + if (assigneesToAddArray.length === 0 && assigneesToRemoveArray.length === 0) { + closePopoverMenu(); + return; + } + + const ids = alertItems.map((item) => item._id); + const assignees = { + add: assigneesToAddArray, + remove: assigneesToRemoveArray, + }; + const onSuccess = () => { + if (refresh) refresh(); + if (clearSelection) clearSelection(); + }; + if (onSubmit != null) { + closePopoverMenu(); + await onSubmit(assignees, ids, onSuccess, setIsLoading); + } + }, + [alertItems, assignedUserIds, clearSelection, closePopoverMenu, onSubmit, refresh, setIsLoading] + ); + + return ( +
+ +
+ ); +}; + +export const BulkAlertAssigneesPanel = memo(BulkAlertAssigneesPanelComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/translations.ts index a99ad3cb76a43..8799911b6e306 100644 --- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/translations.ts @@ -211,3 +211,31 @@ export const ALERT_TAGS_CONTEXT_MENU_ITEM_TOOLTIP_INFO = i18n.translate( defaultMessage: 'Change alert tag options in Kibana Advanced Settings.', } ); + +export const UPDATE_ALERT_ASSIGNEES_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.securitySolution.bulkActions.updateAlertAssigneesSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully updated assignees for {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const UPDATE_ALERT_ASSIGNEES_FAILURE = i18n.translate( + 'xpack.securitySolution.bulkActions.updateAlertAssigneesFailedToastMessage', + { + defaultMessage: 'Failed to update alert assignees.', + } +); + +export const ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE = i18n.translate( + 'xpack.securitySolution.bulkActions.alertAssigneesContextMenuItemTitle', + { + defaultMessage: 'Assign alert', + } +); + +export const REMOVE_ALERT_ASSIGNEES_CONTEXT_MENU_TITLE = i18n.translate( + 'xpack.securitySolution.bulkActions.removeAlertAssignessContextMenuTitle', + { + defaultMessage: 'Unassign alert', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items.test.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items.test.tsx new file mode 100644 index 0000000000000..7a6b9c87fa27e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items.test.tsx @@ -0,0 +1,192 @@ +/* + * 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 { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; +import { TestProviders } from '@kbn/timelines-plugin/public/mock'; +import type { BulkActionsConfig } from '@kbn/triggers-actions-ui-plugin/public/types'; +import type { TimelineItem } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alerts_table/bulk_actions/components/toolbar'; +import { act, fireEvent, render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; + +import type { + UseBulkAlertAssigneesItemsProps, + UseBulkAlertAssigneesPanel, +} from './use_bulk_alert_assignees_items'; +import { useBulkAlertAssigneesItems } from './use_bulk_alert_assignees_items'; +import { useSetAlertAssignees } from './use_set_alert_assignees'; +import { useGetCurrentUserProfile } from '../../user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../../user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../../user_profiles/use_suggest_users'; +import { ASSIGNEES_APPLY_BUTTON_TEST_ID } from '../../assignees/test_ids'; +import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'; +import { useLicense } from '../../../hooks/use_license'; + +jest.mock('./use_set_alert_assignees'); +jest.mock('../../user_profiles/use_get_current_user_profile'); +jest.mock('../../user_profiles/use_bulk_get_user_profiles'); +jest.mock('../../user_profiles/use_suggest_users'); +jest.mock('../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'); +jest.mock('../../../hooks/use_license'); + +const mockUserProfiles = [ + { uid: 'user-id-1', enabled: true, user: { username: 'fakeUser1' }, data: {} }, + { uid: 'user-id-2', enabled: true, user: { username: 'fakeUser2' }, data: {} }, +]; + +const defaultProps: UseBulkAlertAssigneesItemsProps = { + onAssigneesUpdate: () => {}, +}; + +const mockAssigneeItems = [ + { + _id: 'test-id', + data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['user-id-1', 'user-id-2'] }], + ecs: { _id: 'test-id', _index: 'test-index' }, + }, +]; + +const renderPanel = (panel: UseBulkAlertAssigneesPanel) => { + const content = panel.renderContent({ + closePopoverMenu: jest.fn(), + setIsBulkActionsLoading: jest.fn(), + alertItems: mockAssigneeItems, + }); + return render(content); +}; + +describe('useBulkAlertAssigneesItems', () => { + beforeEach(() => { + (useSetAlertAssignees as jest.Mock).mockReturnValue(jest.fn()); + (useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], + }); + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + (useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: true }); + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => true }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return two alert assignees action items and one panel', () => { + const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), { + wrapper: TestProviders, + }); + + expect(result.current.alertAssigneesItems.length).toEqual(2); + expect(result.current.alertAssigneesPanels.length).toEqual(1); + + expect(result.current.alertAssigneesItems[0]['data-test-subj']).toEqual( + 'alert-assignees-context-menu-item' + ); + expect(result.current.alertAssigneesItems[1]['data-test-subj']).toEqual( + 'remove-alert-assignees-menu-item' + ); + expect(result.current.alertAssigneesPanels[0]['data-test-subj']).toEqual( + 'alert-assignees-context-menu-panel' + ); + }); + + it('should still render alert assignees panel when useSetAlertAssignees is null', () => { + (useSetAlertAssignees as jest.Mock).mockReturnValue(null); + const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), { + wrapper: TestProviders, + }); + + expect(result.current.alertAssigneesPanels[0]['data-test-subj']).toEqual( + 'alert-assignees-context-menu-panel' + ); + const wrapper = renderPanel(result.current.alertAssigneesPanels[0]); + expect(wrapper.getByTestId('alert-assignees-selectable-menu')).toBeInTheDocument(); + }); + + it('should call setAlertAssignees on submit', () => { + const mockSetAlertAssignees = jest.fn(); + (useSetAlertAssignees as jest.Mock).mockReturnValue(mockSetAlertAssignees); + const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), { + wrapper: TestProviders, + }); + + const wrapper = renderPanel(result.current.alertAssigneesPanels[0]); + expect(wrapper.getByTestId('alert-assignees-selectable-menu')).toBeInTheDocument(); + act(() => { + fireEvent.click(wrapper.getByText('fakeUser2')); // Won't fire unless component assignees selection has been changed + }); + act(() => { + fireEvent.click(wrapper.getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID)); + }); + expect(mockSetAlertAssignees).toHaveBeenCalled(); + }); + + it('should call setAlertAssignees with the correct parameters on `Unassign alert` button click', () => { + const mockSetAlertAssignees = jest.fn(); + (useSetAlertAssignees as jest.Mock).mockReturnValue(mockSetAlertAssignees); + const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), { + wrapper: TestProviders, + }); + + const items: TimelineItem[] = [ + { + _id: 'alert1', + data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['user1', 'user2'] }], + ecs: { _id: 'alert1', _index: 'index1' }, + }, + { + _id: 'alert2', + data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['user1', 'user3'] }], + ecs: { _id: 'alert2', _index: 'index1' }, + }, + { + _id: 'alert3', + data: [], + ecs: { _id: 'alert3', _index: 'index1' }, + }, + ]; + + const setAlertLoadingMock = jest.fn(); + ( + result.current.alertAssigneesItems[1] as unknown as { onClick: BulkActionsConfig['onClick'] } + ).onClick?.(items, true, setAlertLoadingMock, jest.fn(), jest.fn()); + + expect(mockSetAlertAssignees).toHaveBeenCalled(); + expect(mockSetAlertAssignees).toHaveBeenCalledWith( + { add: [], remove: ['user1', 'user2', 'user3'] }, + ['alert1', 'alert2', 'alert3'], + expect.any(Function), + setAlertLoadingMock + ); + }); + + it('should return 0 items for the VIEWER role', () => { + (useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: false }); + + const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), { + wrapper: TestProviders, + }); + expect(result.current.alertAssigneesItems.length).toEqual(0); + expect(result.current.alertAssigneesPanels.length).toEqual(0); + }); + + it('should return 0 items for the Basic license', () => { + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => false }); + + const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), { + wrapper: TestProviders, + }); + expect(result.current.alertAssigneesItems.length).toEqual(0); + expect(result.current.alertAssigneesPanels.length).toEqual(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items.tsx new file mode 100644 index 0000000000000..46fed23c1214b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items.tsx @@ -0,0 +1,158 @@ +/* + * 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 { union } from 'lodash'; +import React, { useCallback, useMemo } from 'react'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; +import type { + BulkActionsConfig, + RenderContentPanelProps, +} from '@kbn/triggers-actions-ui-plugin/public/types'; + +import { useLicense } from '../../../hooks/use_license'; +import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'; +import { ASSIGNEES_PANEL_WIDTH } from '../../assignees/constants'; +import { BulkAlertAssigneesPanel } from './alert_bulk_assignees'; +import * as i18n from './translations'; +import { useSetAlertAssignees } from './use_set_alert_assignees'; + +export interface UseBulkAlertAssigneesItemsProps { + onAssigneesUpdate?: () => void; +} + +export interface UseBulkAlertAssigneesPanel { + id: number; + title: JSX.Element; + 'data-test-subj': string; + renderContent: (props: RenderContentPanelProps) => JSX.Element; + width?: number; +} + +export const useBulkAlertAssigneesItems = ({ + onAssigneesUpdate, +}: UseBulkAlertAssigneesItemsProps) => { + const isPlatinumPlus = useLicense().isPlatinumPlus(); + + const { hasIndexWrite } = useAlertsPrivileges(); + const setAlertAssignees = useSetAlertAssignees(); + + const handleOnAlertAssigneesSubmit = useCallback( + async (assignees, ids, onSuccess, setIsLoading) => { + if (setAlertAssignees) { + await setAlertAssignees(assignees, ids, onSuccess, setIsLoading); + } + }, + [setAlertAssignees] + ); + + const onSuccess = useCallback(() => { + onAssigneesUpdate?.(); + }, [onAssigneesUpdate]); + + const onRemoveAllAssignees = useCallback['onClick']>( + async (items, _, setAlertLoading) => { + const ids: string[] = items.map((item) => item._id); + const assignedUserIds = union( + ...items.map( + (item) => + item.data.find((data) => data.field === ALERT_WORKFLOW_ASSIGNEE_IDS)?.value ?? [] + ) + ); + if (!assignedUserIds.length) { + return; + } + const assignees = { + add: [], + remove: assignedUserIds, + }; + if (setAlertAssignees) { + await setAlertAssignees(assignees, ids, onSuccess, setAlertLoading); + } + }, + [onSuccess, setAlertAssignees] + ); + + const alertAssigneesItems = useMemo( + () => + hasIndexWrite && isPlatinumPlus + ? [ + { + key: 'manage-alert-assignees', + 'data-test-subj': 'alert-assignees-context-menu-item', + name: i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, + panel: 2, + label: i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, + disableOnQuery: true, + }, + { + key: 'remove-all-alert-assignees', + 'data-test-subj': 'remove-alert-assignees-menu-item', + name: i18n.REMOVE_ALERT_ASSIGNEES_CONTEXT_MENU_TITLE, + label: i18n.REMOVE_ALERT_ASSIGNEES_CONTEXT_MENU_TITLE, + disableOnQuery: true, + onClick: onRemoveAllAssignees, + }, + ] + : [], + [hasIndexWrite, isPlatinumPlus, onRemoveAllAssignees] + ); + + const TitleContent = useMemo( + () => ( + + {i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE} + + ), + [] + ); + + const renderContent = useCallback( + ({ + alertItems, + refresh, + setIsBulkActionsLoading, + clearSelection, + closePopoverMenu, + }: RenderContentPanelProps) => ( + { + onSuccess(); + refresh?.(); + }} + setIsLoading={setIsBulkActionsLoading} + clearSelection={clearSelection} + closePopoverMenu={closePopoverMenu} + onSubmit={handleOnAlertAssigneesSubmit} + /> + ), + [handleOnAlertAssigneesSubmit, onSuccess] + ); + + const alertAssigneesPanels: UseBulkAlertAssigneesPanel[] = useMemo( + () => + hasIndexWrite && isPlatinumPlus + ? [ + { + id: 2, + title: TitleContent, + 'data-test-subj': 'alert-assignees-context-menu-panel', + renderContent, + width: ASSIGNEES_PANEL_WIDTH, + }, + ] + : [], + [TitleContent, hasIndexWrite, isPlatinumPlus, renderContent] + ); + + return { + alertAssigneesItems, + alertAssigneesPanels, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_assignees.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_assignees.tsx new file mode 100644 index 0000000000000..43630cda420c7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_set_alert_assignees.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 type { CoreStart } from '@kbn/core/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useCallback, useEffect, useRef } from 'react'; +import type { AlertAssignees } from '../../../../../common/api/detection_engine'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import * as i18n from './translations'; +import { setAlertAssignees } from '../../../containers/alert_assignees/api'; + +export type SetAlertAssigneesFunc = ( + assignees: AlertAssignees, + ids: string[], + onSuccess: () => void, + setTableLoading: (param: boolean) => void +) => Promise; +export type ReturnSetAlertAssignees = SetAlertAssigneesFunc | null; + +/** + * Update alert assignees by query + * + * @param assignees to add and/or remove from a batch of alerts + * @param ids alert ids that will be used to create the update query. + * @param onSuccess a callback function that will be called on successful api response + * @param setTableLoading a function that sets the alert table in a loading state for bulk actions + + * + * @throws An error if response is not OK + */ +export const useSetAlertAssignees = (): ReturnSetAlertAssignees => { + const { http } = useKibana().services; + const { addSuccess, addError } = useAppToasts(); + const setAlertAssigneesRef = useRef(null); + + const onUpdateSuccess = useCallback( + (updated: number = 0) => addSuccess(i18n.UPDATE_ALERT_ASSIGNEES_SUCCESS_TOAST(updated)), + [addSuccess] + ); + + const onUpdateFailure = useCallback( + (error: Error) => { + addError(error.message, { title: i18n.UPDATE_ALERT_ASSIGNEES_FAILURE }); + }, + [addError] + ); + + useEffect(() => { + let ignore = false; + const abortCtrl = new AbortController(); + + const onSetAlertAssignees: SetAlertAssigneesFunc = async ( + assignees, + ids, + onSuccess, + setTableLoading + ) => { + try { + setTableLoading(true); + const response = await setAlertAssignees({ assignees, ids, signal: abortCtrl.signal }); + if (!ignore) { + onSuccess(); + setTableLoading(false); + onUpdateSuccess(response.updated); + } + } catch (error) { + if (!ignore) { + setTableLoading(false); + onUpdateFailure(error); + } + } + }; + + setAlertAssigneesRef.current = onSetAlertAssignees; + return (): void => { + ignore = true; + abortCtrl.abort(); + }; + }, [http, onUpdateFailure, onUpdateSuccess]); + + return setAlertAssigneesRef.current; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/__mocks__/api.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/__mocks__/api.ts new file mode 100644 index 0000000000000..34a24bb4e00f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/__mocks__/api.ts @@ -0,0 +1,15 @@ +/* + * 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 { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import { mockUserProfiles } from '../mock'; + +export const suggestUsers = async ({ + searchTerm, +}: { + searchTerm: string; +}): Promise => Promise.resolve(mockUserProfiles); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts new file mode 100644 index 0000000000000..fbd2ee48c9eb6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts @@ -0,0 +1,46 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; + +import { mockUserProfiles } from './mock'; +import { suggestUsers } from './api'; +import { KibanaServices } from '../../lib/kibana'; +import { DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL } from '../../../../common/constants'; + +const mockKibanaServices = KibanaServices.get as jest.Mock; +jest.mock('../../lib/kibana'); + +const coreStartMock = coreMock.createStart({ basePath: '/mock' }); +mockKibanaServices.mockReturnValue(coreStartMock); +const fetchMock = coreStartMock.http.fetch; + +describe('Detections Alerts API', () => { + describe('suggestUsers', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(mockUserProfiles); + }); + + test('check parameter url', async () => { + await suggestUsers({ searchTerm: 'name1' }); + expect(fetchMock).toHaveBeenCalledWith( + DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL, + expect.objectContaining({ + method: 'GET', + version: '2023-10-31', + query: { searchTerm: 'name1' }, + }) + ); + }); + + test('happy path', async () => { + const alertsResp = await suggestUsers({ searchTerm: '' }); + expect(alertsResp).toEqual(mockUserProfiles); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts new file mode 100644 index 0000000000000..22340d25e0a57 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; + +import type { SuggestUsersProps } from './types'; +import { DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL } from '../../../../common/constants'; +import { KibanaServices } from '../../lib/kibana'; + +/** + * Fetches suggested user profiles + */ +export const suggestUsers = async ({ + searchTerm, +}: SuggestUsersProps): Promise => { + return KibanaServices.get().http.fetch( + DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL, + { + method: 'GET', + version: '2023-10-31', + query: { searchTerm }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/mock.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/mock.ts new file mode 100644 index 0000000000000..11d1535cc5a50 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/mock.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. + */ + +export const mockCurrentUserProfile = { + uid: 'current-user', + enabled: true, + user: { username: 'current.user' }, + data: {}, +}; + +export const mockUserProfiles = [ + { uid: 'user-id-1', enabled: true, user: { username: 'user1' }, data: {} }, + { uid: 'user-id-2', enabled: true, user: { username: 'user2' }, data: {} }, +]; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/test_ids.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/test_ids.ts new file mode 100644 index 0000000000000..6a2aa4e259166 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/test_ids.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +const PREFIX = 'securitySolutionUsers'; + +/* Avatars */ +export const USER_AVATAR_ITEM_TEST_ID = (userName: string) => `${PREFIX}Avatar-${userName}`; +export const USERS_AVATARS_PANEL_TEST_ID = `${PREFIX}AvatarsPanel`; +export const USERS_AVATARS_COUNT_BADGE_TEST_ID = `${PREFIX}AvatarsCountBadge`; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/translations.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/translations.ts new file mode 100644 index 0000000000000..6b749e45e3993 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/translations.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const CURRENT_USER_PROFILE_FAILURE = i18n.translate( + 'xpack.securitySolution.userProfiles.fetchCurrentUserProfile.failure', + { defaultMessage: 'Failed to find current user' } +); + +export const USER_PROFILES_FAILURE = i18n.translate( + 'xpack.securitySolution.userProfiles.fetchUserProfiles.failure', + { + defaultMessage: 'Failed to find users', + } +); + +/** + * Used whenever we need to display a user name and for some reason it is not available + */ +export const UNKNOWN_USER_PROFILE_NAME = i18n.translate( + 'xpack.securitySolution.userProfiles.unknownUser.displayName', + { defaultMessage: 'Unknown' } +); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/types.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/types.ts new file mode 100644 index 0000000000000..2d0586dd571a3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/types.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export interface SuggestUsersProps { + searchTerm: string; +} diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.test.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.test.tsx new file mode 100644 index 0000000000000..3861e6a6c8a67 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { securityMock } from '@kbn/security-plugin/public/mocks'; + +import { mockUserProfiles } from './mock'; +import { useBulkGetUserProfiles } from './use_bulk_get_user_profiles'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useAppToastsMock } from '../../hooks/use_app_toasts.mock'; +import { createStartServicesMock } from '../../lib/kibana/kibana_react.mock'; +import { TestProviders } from '../../mock'; + +jest.mock('../../lib/kibana'); +jest.mock('../../hooks/use_app_toasts'); + +describe('useBulkGetUserProfiles hook', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { + jest.clearAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + const security = securityMock.createStart(); + security.userProfiles.bulkGet.mockReturnValue(Promise.resolve(mockUserProfiles)); + (useKibana as jest.Mock).mockReturnValue({ + services: { + ...createStartServicesMock(), + security, + }, + }); + }); + + it('returns an array of userProfiles', async () => { + const userProfiles = useKibana().services.security.userProfiles; + const spyOnUserProfiles = jest.spyOn(userProfiles, 'bulkGet'); + const assigneesIds = new Set(['user1']); + const { result, waitForNextUpdate } = renderHook( + () => useBulkGetUserProfiles({ uids: assigneesIds }), + { + wrapper: TestProviders, + } + ); + await waitForNextUpdate(); + + expect(spyOnUserProfiles).toHaveBeenCalledTimes(1); + expect(result.current.isLoading).toEqual(false); + expect(result.current.data).toEqual(mockUserProfiles); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.tsx new file mode 100644 index 0000000000000..b74e797162515 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.tsx @@ -0,0 +1,48 @@ +/* + * 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 { SecurityPluginStart } from '@kbn/security-plugin/public'; +import type { UserProfile } from '@kbn/security-plugin/common'; +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import { useQuery } from '@tanstack/react-query'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { USER_PROFILES_FAILURE } from './translations'; + +export interface BulkGetUserProfilesArgs { + security: SecurityPluginStart; + uids: Set; +} + +export const bulkGetUserProfiles = async ({ + security, + uids, +}: BulkGetUserProfilesArgs): Promise => { + if (uids.size === 0) { + return []; + } + return security.userProfiles.bulkGet({ uids, dataPath: 'avatar' }); +}; + +export const useBulkGetUserProfiles = ({ uids }: { uids: Set }) => { + const { security } = useKibana().services; + const { addError } = useAppToasts(); + + return useQuery( + ['useBulkGetUserProfiles', ...uids], + async () => { + return bulkGetUserProfiles({ security, uids }); + }, + { + retry: false, + staleTime: Infinity, + onError: (e) => { + addError(e, { title: USER_PROFILES_FAILURE }); + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.test.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.test.tsx new file mode 100644 index 0000000000000..84beb0a8b135b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.test.tsx @@ -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 { renderHook } from '@testing-library/react-hooks'; +import { securityMock } from '@kbn/security-plugin/public/mocks'; + +import { mockCurrentUserProfile } from './mock'; +import { useGetCurrentUserProfile } from './use_get_current_user_profile'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useAppToastsMock } from '../../hooks/use_app_toasts.mock'; +import { createStartServicesMock } from '../../lib/kibana/kibana_react.mock'; +import { TestProviders } from '../../mock'; + +jest.mock('../../lib/kibana'); +jest.mock('../../hooks/use_app_toasts'); + +describe('useGetCurrentUserProfile hook', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { + jest.clearAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + const security = securityMock.createStart(); + security.userProfiles.getCurrent.mockReturnValue(Promise.resolve(mockCurrentUserProfile)); + (useKibana as jest.Mock).mockReturnValue({ + services: { + ...createStartServicesMock(), + security, + }, + }); + }); + + it('returns current user', async () => { + const userProfiles = useKibana().services.security.userProfiles; + const spyOnUserProfiles = jest.spyOn(userProfiles, 'getCurrent'); + const { result, waitForNextUpdate } = renderHook(() => useGetCurrentUserProfile(), { + wrapper: TestProviders, + }); + await waitForNextUpdate(); + + expect(spyOnUserProfiles).toHaveBeenCalledTimes(1); + expect(result.current.isLoading).toEqual(false); + expect(result.current.data).toEqual(mockCurrentUserProfile); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.tsx new file mode 100644 index 0000000000000..fbb4bb0660407 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.tsx @@ -0,0 +1,47 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { SecurityPluginStart } from '@kbn/security-plugin/public'; +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; + +import { CURRENT_USER_PROFILE_FAILURE } from './translations'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; + +export const getCurrentUserProfile = async ({ + security, +}: { + security: SecurityPluginStart; +}): Promise => { + return security.userProfiles.getCurrent({ dataPath: 'avatar' }); +}; + +/** + * Fetches current user profile using `userProfiles` service via `security.userProfiles.getCurrent()` + * + * NOTE: There is a similar hook `useCurrentUser` which fetches current authenticated user via `security.authc.getCurrentUser()` + */ +export const useGetCurrentUserProfile = () => { + const { security } = useKibana().services; + const { addError } = useAppToasts(); + + return useQuery( + ['useGetCurrentUserProfile'], + async () => { + return getCurrentUserProfile({ security }); + }, + { + retry: false, + staleTime: Infinity, + onError: (e) => { + addError(e, { title: CURRENT_USER_PROFILE_FAILURE }); + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/use_suggest_users.test.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_suggest_users.test.tsx new file mode 100644 index 0000000000000..2cb727942ed57 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_suggest_users.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useSuggestUsers } from './use_suggest_users'; + +import * as api from './api'; +import { mockUserProfiles } from './mock'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useAppToastsMock } from '../../hooks/use_app_toasts.mock'; +import { TestProviders } from '../../mock'; + +jest.mock('./api'); +jest.mock('../../hooks/use_app_toasts'); + +describe('useSuggestUsers hook', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { + jest.clearAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + + it('returns an array of userProfiles', async () => { + const spyOnUserProfiles = jest.spyOn(api, 'suggestUsers'); + const { result, waitForNextUpdate } = renderHook(() => useSuggestUsers({ searchTerm: '' }), { + wrapper: TestProviders, + }); + await waitForNextUpdate(); + expect(spyOnUserProfiles).toHaveBeenCalledTimes(1); + expect(result.current.isLoading).toEqual(false); + expect(result.current.data).toEqual(mockUserProfiles); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/use_suggest_users.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_suggest_users.tsx new file mode 100644 index 0000000000000..a8a2338e51e9d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/use_suggest_users.tsx @@ -0,0 +1,44 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; + +import { suggestUsers } from './api'; +import { USER_PROFILES_FAILURE } from './translations'; +import { useAppToasts } from '../../hooks/use_app_toasts'; + +export interface SuggestUserProfilesArgs { + searchTerm: string; +} + +export const bulkGetUserProfiles = async ({ + searchTerm, +}: { + searchTerm: string; +}): Promise => { + return suggestUsers({ searchTerm }); +}; + +export const useSuggestUsers = ({ searchTerm }: { searchTerm: string }) => { + const { addError } = useAppToasts(); + + return useQuery( + ['useSuggestUsers', searchTerm], + async () => { + return bulkGetUserProfiles({ searchTerm }); + }, + { + retry: false, + staleTime: Infinity, + onError: (e) => { + addError(e, { title: USER_PROFILES_FAILURE }); + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/users_avatars_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/users_avatars_panel.test.tsx new file mode 100644 index 0000000000000..725cb81aea3e0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/users_avatars_panel.test.tsx @@ -0,0 +1,58 @@ +/* + * 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 { render } from '@testing-library/react'; + +import { UsersAvatarsPanel } from './users_avatars_panel'; + +import { TestProviders } from '../../mock'; +import { mockUserProfiles } from '../assignees/mocks'; +import { + USERS_AVATARS_COUNT_BADGE_TEST_ID, + USERS_AVATARS_PANEL_TEST_ID, + USER_AVATAR_ITEM_TEST_ID, +} from './test_ids'; + +const renderUsersAvatarsPanel = (userProfiles = [mockUserProfiles[0]], maxVisibleAvatars = 1) => + render( + + + + ); + +describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render component', () => { + const { getByTestId } = renderUsersAvatarsPanel(); + + expect(getByTestId(USERS_AVATARS_PANEL_TEST_ID)).toBeInTheDocument(); + }); + + it('should render avatars for all assignees', () => { + const assignees = [mockUserProfiles[0], mockUserProfiles[1]]; + const { getByTestId, queryByTestId } = renderUsersAvatarsPanel(assignees, 2); + + expect(getByTestId(USER_AVATAR_ITEM_TEST_ID('user1'))).toBeInTheDocument(); + expect(getByTestId(USER_AVATAR_ITEM_TEST_ID('user2'))).toBeInTheDocument(); + + expect(queryByTestId(USERS_AVATARS_COUNT_BADGE_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render badge with number of assignees if exceeds `maxVisibleAvatars`', () => { + const assignees = [mockUserProfiles[0], mockUserProfiles[1]]; + const { getByTestId, queryByTestId } = renderUsersAvatarsPanel(assignees, 1); + + expect(getByTestId(USERS_AVATARS_COUNT_BADGE_TEST_ID)).toBeInTheDocument(); + + expect(queryByTestId(USER_AVATAR_ITEM_TEST_ID('user1'))).not.toBeInTheDocument(); + expect(queryByTestId(USER_AVATAR_ITEM_TEST_ID('user2'))).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/users_avatars_panel.tsx b/x-pack/plugins/security_solution/public/common/components/user_profiles/users_avatars_panel.tsx new file mode 100644 index 0000000000000..777ef04060f2b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/users_avatars_panel.tsx @@ -0,0 +1,80 @@ +/* + * 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 { FC } from 'react'; +import React, { memo } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiToolTip } from '@elastic/eui'; +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import { UserAvatar } from '@kbn/user-profile-components'; + +import { UNKNOWN_USER_PROFILE_NAME } from './translations'; +import { + USERS_AVATARS_COUNT_BADGE_TEST_ID, + USERS_AVATARS_PANEL_TEST_ID, + USER_AVATAR_ITEM_TEST_ID, +} from './test_ids'; + +export type UserProfileOrUknown = UserProfileWithAvatar | undefined; + +export interface UsersAvatarsPanelProps { + /** + * The array of user profiles + */ + userProfiles: UserProfileOrUknown[]; + + /** + * Specifies how many avatars should be visible. + * If more assignees passed, then badge with number of assignees will be shown instead. + */ + maxVisibleAvatars?: number; +} + +/** + * Displays users avatars + */ +export const UsersAvatarsPanel: FC = memo( + ({ userProfiles, maxVisibleAvatars }) => { + if (maxVisibleAvatars && userProfiles.length > maxVisibleAvatars) { + return ( + ( +
{user ? user.user.email ?? user.user.username : UNKNOWN_USER_PROFILE_NAME}
+ ))} + repositionOnScroll={true} + > + + {userProfiles.length} + +
+ ); + } + + return ( + + {userProfiles.map((user, index) => ( + + + + ))} + + ); + } +); + +UsersAvatarsPanel.displayName = 'UsersAvatarsPanel'; diff --git a/x-pack/plugins/security_solution/public/common/containers/alert_assignees/api.ts b/x-pack/plugins/security_solution/public/common/containers/alert_assignees/api.ts new file mode 100644 index 0000000000000..8652a51138d62 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/alert_assignees/api.ts @@ -0,0 +1,31 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '../../../../common/constants'; +import type { AlertAssignees } from '../../../../common/api/detection_engine'; +import { KibanaServices } from '../../lib/kibana'; + +export const setAlertAssignees = async ({ + assignees, + ids, + signal, +}: { + assignees: AlertAssignees; + ids: string[]; + signal: AbortSignal | undefined; +}): Promise => { + return KibanaServices.get().http.fetch( + DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + { + method: 'POST', + version: '2023-10-31', + body: JSON.stringify({ assignees, ids }), + signal, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx b/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx index ff75b25832267..6db7a5c814f6f 100644 --- a/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx @@ -23,6 +23,7 @@ import { import { getDataTablesInStorageByIds } from '../../../timelines/containers/local_storage'; import { getColumns } from '../../../detections/configurations/security_solution_detections'; import { getRenderCellValueHook } from '../../../detections/configurations/security_solution_detections/render_cell_value'; +import { useFetchPageContext } from '../../../detections/configurations/security_solution_detections/fetch_page_context'; import { SourcererScopeName } from '../../store/sourcerer/model'; const registerAlertsTableConfiguration = ( @@ -64,6 +65,7 @@ const registerAlertsTableConfiguration = ( sort, useFieldBrowserOptions: getUseTriggersActionsFieldBrowserOptions(SourcererScopeName.detections), showInspectButton: true, + useFetchPageContext, }); // register Alert Table on RuleDetails Page @@ -79,6 +81,7 @@ const registerAlertsTableConfiguration = ( sort, useFieldBrowserOptions: getUseTriggersActionsFieldBrowserOptions(SourcererScopeName.detections), showInspectButton: true, + useFetchPageContext, }); registerIfNotAlready(registry, { @@ -91,6 +94,7 @@ const registerAlertsTableConfiguration = ( useCellActions: getUseCellActionsHook(TableId.alertsOnCasePage), sort, showInspectButton: true, + useFetchPageContext, }); registerIfNotAlready(registry, { @@ -104,6 +108,7 @@ const registerAlertsTableConfiguration = ( usePersistentControls: getPersistentControlsHook(TableId.alertsRiskInputs), sort, showInspectButton: true, + useFetchPageContext, }); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx index 8807ccf0388d2..b4b0a07fd7ab2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx @@ -7,6 +7,7 @@ import type { ExistsFilter, Filter } from '@kbn/es-query'; import { + buildAlertAssigneesFilter, buildAlertsFilter, buildAlertStatusesFilter, buildAlertStatusFilter, @@ -158,6 +159,47 @@ describe('alerts default_config', () => { }); }); + describe('buildAlertAssigneesFilter', () => { + test('given an empty list of assignees ids will return an empty filter', () => { + const filters: Filter[] = buildAlertAssigneesFilter([]); + expect(filters).toHaveLength(0); + }); + + test('builds filter containing all assignees ids passed into function', () => { + const filters = buildAlertAssigneesFilter(['user-id-1', 'user-id-2', 'user-id-3']); + const expected = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { + bool: { + should: [ + { + term: { + 'kibana.alert.workflow_assignee_ids': 'user-id-1', + }, + }, + { + term: { + 'kibana.alert.workflow_assignee_ids': 'user-id-2', + }, + }, + { + term: { + 'kibana.alert.workflow_assignee_ids': 'user-id-3', + }, + }, + ], + }, + }, + }; + expect(filters).toHaveLength(1); + expect(filters[0]).toEqual(expected); + }); + }); + // TODO: move these tests to ../timelines/components/timeline/body/events/event_column_view.tsx // describe.skip('getAlertActions', () => { // let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 1addd05eb8d96..6065e617c1254 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -9,11 +9,13 @@ import { ALERT_BUILDING_BLOCK_TYPE, ALERT_WORKFLOW_STATUS, ALERT_RULE_RULE_ID, + ALERT_WORKFLOW_ASSIGNEE_IDS, } from '@kbn/rule-data-utils'; import type { Filter } from '@kbn/es-query'; import { tableDefaults } from '@kbn/securitysolution-data-table'; import type { SubsetDataTableModel } from '@kbn/securitysolution-data-table'; +import type { AssigneesIdsSelection } from '../../../common/components/assignees/types'; import type { Status } from '../../../../common/api/detection_engine'; import { getColumns, @@ -152,6 +154,36 @@ export const buildThreatMatchFilter = (showOnlyThreatIndicatorAlerts: boolean): ] : []; +export const buildAlertAssigneesFilter = (assigneesIds: AssigneesIdsSelection[]): Filter[] => { + if (!assigneesIds.length) { + return []; + } + const combinedQuery = { + bool: { + should: assigneesIds.map((id) => + id + ? { + term: { + [ALERT_WORKFLOW_ASSIGNEE_IDS]: id, + }, + } + : { bool: { must_not: { exists: { field: ALERT_WORKFLOW_ASSIGNEE_IDS } } } } + ), + }, + }; + + return [ + { + meta: { + alias: null, + negate: false, + disabled: false, + }, + query: combinedQuery, + }, + ]; +}; + export const getAlertsDefaultModel = (license?: LicenseService): SubsetDataTableModel => ({ ...tableDefaults, columns: getColumns(license), @@ -177,6 +209,7 @@ export const requiredFieldsForActions = [ '@timestamp', 'kibana.alert.workflow_status', 'kibana.alert.workflow_tags', + 'kibana.alert.workflow_assignee_ids', 'kibana.alert.group.id', 'kibana.alert.original_time', 'kibana.alert.building_block_type', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx index a8e13bb8c5c27..b91db35cdeaa7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx @@ -28,6 +28,10 @@ jest.mock('../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), })); +jest.mock('../../../../common/hooks/use_license', () => ({ + useLicense: jest.fn().mockReturnValue({ isPlatinumPlus: () => true }), +})); + const ecsRowData: Ecs = { _id: '1', agent: { type: ['blah'] }, @@ -105,6 +109,7 @@ const markAsAcknowledgedButton = '[data-test-subj="acknowledged-alert-status"]'; const markAsClosedButton = '[data-test-subj="close-alert-status"]'; const addEndpointEventFilterButton = '[data-test-subj="add-event-filter-menu-item"]'; const applyAlertTagsButton = '[data-test-subj="alert-tags-context-menu-item"]'; +const applyAlertAssigneesButton = '[data-test-subj="alert-assignees-context-menu-item"]'; describe('Alert table context menu', () => { describe('Case actions', () => { @@ -304,4 +309,16 @@ describe('Alert table context menu', () => { expect(wrapper.find(applyAlertTagsButton).first().exists()).toEqual(true); }); }); + + describe('Assign alert action', () => { + test('it renders the assign alert action button', () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + + wrapper.find(actionMenuButton).simulate('click'); + + expect(wrapper.find(applyAlertAssigneesButton).first().exists()).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 741039c09e0e2..c5c5b95abbccd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -50,6 +50,7 @@ import { isAlertFromEndpointAlert } from '../../../../common/utils/endpoint_aler import type { Rule } from '../../../../detection_engine/rule_management/logic/types'; import type { AlertTableContextMenuItem } from '../types'; import { useAlertTagsActions } from './use_alert_tags_actions'; +import { useAlertAssigneesActions } from './use_alert_assignees_actions'; interface AlertContextMenuProps { ariaLabel?: string; @@ -224,6 +225,12 @@ const AlertContextMenuComponent: React.FC = ({ refetch: refetchAll, }); + const { alertAssigneesItems, alertAssigneesPanels } = useAlertAssigneesActions({ + closePopover, + ecsRowData, + refetch: refetchAll, + }); + const items: AlertTableContextMenuItem[] = useMemo( () => !isEvent && ruleId @@ -231,6 +238,7 @@ const AlertContextMenuComponent: React.FC = ({ ...addToCaseActionItems, ...statusActionItems, ...alertTagsItems, + ...alertAssigneesItems, ...exceptionActionItems, ...(agentId ? osqueryActionItems : []), ] @@ -250,6 +258,7 @@ const AlertContextMenuComponent: React.FC = ({ eventFilterActionItems, canCreateEndpointEventFilters, alertTagsItems, + alertAssigneesItems, ] ); @@ -260,8 +269,9 @@ const AlertContextMenuComponent: React.FC = ({ items, }, ...alertTagsPanels, + ...alertAssigneesPanels, ], - [alertTagsPanels, items] + [alertTagsPanels, alertAssigneesPanels, items] ); const osqueryFlyout = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx new file mode 100644 index 0000000000000..52c196fa729fc --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx @@ -0,0 +1,203 @@ +/* + * 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 { TestProviders } from '@kbn/timelines-plugin/public/mock'; +import { renderHook } from '@testing-library/react-hooks'; +import type { UseAlertAssigneesActionsProps } from './use_alert_assignees_actions'; +import { useAlertAssigneesActions } from './use_alert_assignees_actions'; +import { useAlertsPrivileges } from '../../../containers/detection_engine/alerts/use_alerts_privileges'; +import type { AlertTableContextMenuItem } from '../types'; +import { render } from '@testing-library/react'; +import React from 'react'; +import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import { EuiPopover, EuiContextMenu } from '@elastic/eui'; +import { useSetAlertAssignees } from '../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees'; +import { useGetCurrentUserProfile } from '../../../../common/components/user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../../../../common/components/user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../../../../common/components/user_profiles/use_suggest_users'; +import { useLicense } from '../../../../common/hooks/use_license'; + +jest.mock('../../../containers/detection_engine/alerts/use_alerts_privileges'); +jest.mock('../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees'); +jest.mock('../../../../common/components/user_profiles/use_get_current_user_profile'); +jest.mock('../../../../common/components/user_profiles/use_bulk_get_user_profiles'); +jest.mock('../../../../common/components/user_profiles/use_suggest_users'); +jest.mock('../../../../common/hooks/use_license'); + +const mockUserProfiles = [ + { uid: 'user-id-1', enabled: true, user: { username: 'fakeUser1' }, data: {} }, + { uid: 'user-id-2', enabled: true, user: { username: 'fakeUser2' }, data: {} }, +]; + +const defaultProps: UseAlertAssigneesActionsProps = { + closePopover: jest.fn(), + ecsRowData: { + _id: '123', + kibana: { + alert: { + workflow_assignee_ids: [], + }, + }, + }, + refetch: jest.fn(), +}; + +const renderContextMenu = ( + items: AlertTableContextMenuItem[], + panels: EuiContextMenuPanelDescriptor[] +) => { + const panelsToRender = [{ id: 0, items }, ...panels]; + return render( + {}} + button={<>} + > + + + ); +}; + +describe('useAlertAssigneesActions', () => { + beforeEach(() => { + (useAlertsPrivileges as jest.Mock).mockReturnValue({ + hasIndexWrite: true, + }); + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => true }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render alert assignees actions', () => { + const { result } = renderHook(() => useAlertAssigneesActions(defaultProps), { + wrapper: TestProviders, + }); + + expect(result.current.alertAssigneesItems.length).toEqual(2); + expect(result.current.alertAssigneesPanels.length).toEqual(1); + expect(result.current.alertAssigneesItems[0]['data-test-subj']).toEqual( + 'alert-assignees-context-menu-item' + ); + expect(result.current.alertAssigneesItems[1]['data-test-subj']).toEqual( + 'remove-alert-assignees-menu-item' + ); + + expect(result.current.alertAssigneesPanels[0].content).toMatchInlineSnapshot(` + + `); + }); + + it("should not render alert assignees actions if user doesn't have write permissions", () => { + (useAlertsPrivileges as jest.Mock).mockReturnValue({ + hasIndexWrite: false, + }); + const { result } = renderHook(() => useAlertAssigneesActions(defaultProps), { + wrapper: TestProviders, + }); + expect(result.current.alertAssigneesItems.length).toEqual(0); + }); + + it('should not render alert assignees actions within Basic license', () => { + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => false }); + const { result } = renderHook(() => useAlertAssigneesActions(defaultProps), { + wrapper: TestProviders, + }); + expect(result.current.alertAssigneesItems.length).toEqual(0); + }); + + it('should still render if workflow_assignee_ids field does not exist', () => { + const newProps = { + ...defaultProps, + ecsRowData: { + _id: '123', + }, + }; + const { result } = renderHook(() => useAlertAssigneesActions(newProps), { + wrapper: TestProviders, + }); + expect(result.current.alertAssigneesItems.length).toEqual(2); + expect(result.current.alertAssigneesPanels.length).toEqual(1); + expect(result.current.alertAssigneesPanels[0].content).toMatchInlineSnapshot(` + + `); + }); + + it('should render the nested panel', async () => { + (useSetAlertAssignees as jest.Mock).mockReturnValue(jest.fn()); + (useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], + }); + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + + const { result } = renderHook(() => useAlertAssigneesActions(defaultProps), { + wrapper: TestProviders, + }); + const alertAssigneesItems = result.current.alertAssigneesItems; + const alertAssigneesPanels = result.current.alertAssigneesPanels; + const { getByTestId } = renderContextMenu(alertAssigneesItems, alertAssigneesPanels); + + expect(getByTestId('alert-assignees-selectable-menu')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.tsx new file mode 100644 index 0000000000000..ff7e64a5e4dc8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.tsx @@ -0,0 +1,94 @@ +/* + * 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 { noop } from 'lodash'; +import { useCallback, useMemo } from 'react'; + +import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; + +import { ASSIGNEES_PANEL_WIDTH } from '../../../../common/components/assignees/constants'; +import { useBulkAlertAssigneesItems } from '../../../../common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items'; +import { useAlertsPrivileges } from '../../../containers/detection_engine/alerts/use_alerts_privileges'; +import type { AlertTableContextMenuItem } from '../types'; + +export interface UseAlertAssigneesActionsProps { + closePopover: () => void; + ecsRowData: Ecs; + refetch?: () => void; +} + +export const useAlertAssigneesActions = ({ + closePopover, + ecsRowData, + refetch, +}: UseAlertAssigneesActionsProps) => { + const { hasIndexWrite } = useAlertsPrivileges(); + + const alertId = ecsRowData._id; + const alertAssigneeData = useMemo(() => { + return [ + { + _id: alertId, + _index: ecsRowData._index ?? '', + data: [ + { + field: ALERT_WORKFLOW_ASSIGNEE_IDS, + value: ecsRowData?.kibana?.alert.workflow_assignee_ids ?? [], + }, + ], + ecs: { + _id: alertId, + _index: ecsRowData._index ?? '', + }, + }, + ]; + }, [alertId, ecsRowData._index, ecsRowData?.kibana?.alert.workflow_assignee_ids]); + + const onAssigneesUpdate = useCallback(() => { + closePopover(); + if (refetch) { + refetch(); + } + }, [closePopover, refetch]); + + const { alertAssigneesItems, alertAssigneesPanels } = useBulkAlertAssigneesItems({ + onAssigneesUpdate, + }); + + const itemsToReturn: AlertTableContextMenuItem[] = useMemo( + () => + alertAssigneesItems.map((item) => ({ + name: item.name, + panel: item.panel, + 'data-test-subj': item['data-test-subj'], + key: item.key, + onClick: () => item.onClick?.(alertAssigneeData, false, noop, noop, noop), + })), + [alertAssigneeData, alertAssigneesItems] + ); + + const panelsToReturn: EuiContextMenuPanelDescriptor[] = useMemo( + () => + alertAssigneesPanels.map((panel) => { + const content = panel.renderContent({ + closePopoverMenu: closePopover, + setIsBulkActionsLoading: () => {}, + alertItems: alertAssigneeData, + refresh: onAssigneesUpdate, + }); + return { title: panel.title, content, id: panel.id, width: ASSIGNEES_PANEL_WIDTH }; + }), + [alertAssigneeData, alertAssigneesPanels, closePopover, onAssigneesUpdate] + ); + + return { + alertAssigneesItems: hasIndexWrite ? itemsToReturn : [], + alertAssigneesPanels: panelsToReturn, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 2023df7462536..ae0c05d2bdb05 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -95,6 +95,13 @@ export const ALERTS_HEADERS_RISK_SCORE = i18n.translate( } ); +export const ALERTS_HEADERS_ASSIGNEES = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.assigneesTitle', + { + defaultMessage: 'Assignees', + } +); + export const ALERTS_HEADERS_THRESHOLD_COUNT = i18n.translate( 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.thresholdCount', { diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.test.tsx index 984e19a879637..b099acb254538 100644 --- a/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.test.tsx @@ -23,6 +23,35 @@ jest.mock('../../../common/components/filter_group'); jest.mock('../../../common/lib/kibana'); +const mockUserProfiles = [ + { + uid: 'user-id-1', + enabled: true, + user: { username: 'user1', full_name: 'User 1', email: 'user1@test.com' }, + data: {}, + }, + { + uid: 'user-id-2', + enabled: true, + user: { username: 'user2', full_name: 'User 2', email: 'user2@test.com' }, + data: {}, + }, + { + uid: 'user-id-3', + enabled: true, + user: { username: 'user3', full_name: 'User 3', email: 'user3@test.com' }, + data: {}, + }, +]; +jest.mock('../../../common/components/user_profiles/use_suggest_users', () => { + return { + useSuggestUsers: () => ({ + loading: false, + userProfiles: mockUserProfiles, + }), + }; +}); + const basicKibanaServicesMock = createStartServicesMock(); const getFieldByNameMock = jest.fn(() => true); diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx index cf67bf45fd360..a3e7b942953fb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx @@ -37,7 +37,10 @@ import { getUserPrivilegesMockDefaultValue } from '../../../common/components/us import { allCasesPermissions } from '../../../cases_test_utils'; import { HostStatus } from '../../../../common/endpoint/types'; import { ENDPOINT_CAPABILITIES } from '../../../../common/endpoint/service/response_actions/constants'; -import { ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE } from '../../../common/components/toolbar/bulk_actions/translations'; +import { + ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, + ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE, +} from '../../../common/components/toolbar/bulk_actions/translations'; jest.mock('../../../common/components/user_privileges'); @@ -58,6 +61,10 @@ jest.mock('../../../common/hooks/use_app_toasts', () => ({ }), })); +jest.mock('../../../common/hooks/use_license', () => ({ + useLicense: jest.fn().mockReturnValue({ isPlatinumPlus: () => true }), +})); + jest.mock('../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), })); @@ -254,6 +261,13 @@ describe('take action dropdown', () => { ).toEqual(ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE); }); }); + test('should render "Assign alert"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="alert-assignees-context-menu-item"]').first().text() + ).toEqual(ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE); + }); + }); }); describe('for Endpoint related actions', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index 67175f05ece2e..f8efc47d3bd1a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -35,6 +35,7 @@ import { useKibana } from '../../../common/lib/kibana'; import { getOsqueryActionItem } from '../osquery/osquery_action_item'; import type { AlertTableContextMenuItem } from '../alerts_table/types'; import { useAlertTagsActions } from '../alerts_table/timeline_actions/use_alert_tags_actions'; +import { useAlertAssigneesActions } from '../alerts_table/timeline_actions/use_alert_assignees_actions'; interface ActionsData { alertStatus: Status; @@ -189,6 +190,20 @@ export const TakeActionDropdown = React.memo( refetch, }); + const onAssigneesUpdate = useCallback(() => { + if (refetch) { + refetch(); + } + if (refetchFlyoutData) { + refetchFlyoutData(); + } + }, [refetch, refetchFlyoutData]); + const { alertAssigneesItems, alertAssigneesPanels } = useAlertAssigneesActions({ + closePopover: closePopoverHandler, + ecsRowData: ecsData ?? { _id: actionsData.eventId }, + refetch: onAssigneesUpdate, + }); + const { investigateInTimelineActionItems } = useInvestigateInTimeline({ ecsRowData: ecsData, onInvestigateInTimelineAlertClick: closePopoverHandler, @@ -214,7 +229,12 @@ export const TakeActionDropdown = React.memo( const alertsActionItems = useMemo( () => !isEvent && actionsData.ruleId - ? [...statusActionItems, ...alertTagsItems, ...exceptionActionItems] + ? [ + ...statusActionItems, + ...alertTagsItems, + ...alertAssigneesItems, + ...exceptionActionItems, + ] : isEndpointEvent && canCreateEndpointEventFilters ? eventFilterActionItems : [], @@ -227,6 +247,7 @@ export const TakeActionDropdown = React.memo( isEvent, actionsData.ruleId, alertTagsItems, + alertAssigneesItems, ] ); @@ -271,6 +292,7 @@ export const TakeActionDropdown = React.memo( items, }, ...alertTagsPanels, + ...alertAssigneesPanels, ]; const takeActionButton = useMemo( diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts index 29c8cb4ec0962..bfce842096448 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts @@ -28,6 +28,12 @@ const getBaseColumns = ( > => { const isPlatinumPlus = license?.isPlatinumPlus?.() ?? false; return [ + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_ASSIGNEES, + id: 'kibana.alert.workflow_assignee_ids', + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, + }, { columnHeaderType: defaultColumnHeaderType, displayAsText: i18n.ALERTS_HEADERS_SEVERITY, diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx new file mode 100644 index 0000000000000..ebd8df15d92ea --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx @@ -0,0 +1,43 @@ +/* + * 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 { useMemo } from 'react'; +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import type { PreFetchPageContext } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { useBulkGetUserProfiles } from '../../../common/components/user_profiles/use_bulk_get_user_profiles'; + +export interface RenderCellValueContext { + profiles: UserProfileWithAvatar[] | undefined; + isLoading: boolean; +} + +// Add new columns names to this array to render the user's display name instead of profile_uid +export const profileUidColumns = [ + 'kibana.alert.workflow_assignee_ids', + 'kibana.alert.workflow_user', +]; + +export const useFetchPageContext: PreFetchPageContext = ({ + alerts, + columns, +}) => { + const uids = new Set(); + alerts.forEach((alert) => { + profileUidColumns.forEach((columnId) => { + if (columns.find((column) => column.id === columnId) != null) { + const userUids = alert[columnId]; + userUids?.forEach((uid) => uids.add(uid as string)); + } + }); + }); + const result = useBulkGetUserProfiles({ uids }); + const returnVal = useMemo( + () => ({ profiles: result.data, isLoading: result.isLoading }), + [result.data, result.isLoading] + ); + return returnVal; +}; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx index 47be8b0739346..84e14ad725e40 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx @@ -33,6 +33,7 @@ import { SUPPRESSED_ALERT_TOOLTIP } from './translations'; import { VIEW_SELECTION } from '../../../../common/constants'; import { getAllFieldsByName } from '../../../common/containers/source'; import { eventRenderedViewColumns, getColumns } from './columns'; +import type { RenderCellValueContext } from './fetch_page_context'; /** * This implementation of `EuiDataGrid`'s `renderCellValue` @@ -95,7 +96,7 @@ export const getRenderCellValueHook = ({ scopeId: SourcererScopeName; tableId: TableId; }) => { - const useRenderCellValue: GetRenderCellValue = () => { + const useRenderCellValue: GetRenderCellValue = ({ context }) => { const { browserFields } = useSourcererDataView(scopeId); const browserFieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []); @@ -173,10 +174,11 @@ export const getRenderCellValueHook = ({ scopeId={tableId} truncate={truncate} asPlainText={false} + context={context as RenderCellValueContext} /> ); }, - [browserFieldsByName, browserFields, columnHeaders] + [browserFieldsByName, columnHeaders, browserFields, context] ); return result; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts index 1f667cc42be1e..3af1ddd6c0fce 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts @@ -30,3 +30,8 @@ export const CASES_FROM_ALERTS_FAILURE = i18n.translate( 'xpack.securitySolution.endpoint.hostIsolation.casesFromAlerts.title', { defaultMessage: 'Failed to find associated cases' } ); + +export const USER_PROFILES_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.users.userProfiles.title', + { defaultMessage: 'Failed to find users' } +); diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx index 30e86f6185c33..d468f5b05d869 100644 --- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx @@ -12,6 +12,7 @@ import { isEqual } from 'lodash'; import type { Filter } from '@kbn/es-query'; import { useCallback } from 'react'; import type { TableId } from '@kbn/securitysolution-data-table'; +import { useBulkAlertAssigneesItems } from '../../../common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items'; import { useBulkAlertTagsItems } from '../../../common/components/toolbar/bulk_actions/use_bulk_alert_tags_items'; import type { inputsModel, State } from '../../../common/store'; import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; @@ -93,7 +94,11 @@ export const getBulkActionHook = refetch: refetchGlobalQuery, }); - const items = [...alertActions, timelineAction, ...alertTagsItems]; + const { alertAssigneesItems, alertAssigneesPanels } = useBulkAlertAssigneesItems({ + onAssigneesUpdate: refetchGlobalQuery, + }); + + const items = [...alertActions, timelineAction, ...alertTagsItems, ...alertAssigneesItems]; - return [{ id: 0, items }, ...alertTagsPanels]; + return [{ id: 0, items }, ...alertTagsPanels, ...alertAssigneesPanels]; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index c340c08ad7268..aa196b174131e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -33,6 +33,7 @@ import { FilterGroup } from '../../../common/components/filter_group'; import type { AlertsTableComponentProps } from '../../components/alerts_table/alerts_grouping'; import { getMockedFilterGroupWithCustomFilters } from '../../../common/components/filter_group/mocks'; import { TableId } from '@kbn/securitysolution-data-table'; +import { useUpsellingMessage } from '../../../common/hooks/use_upselling'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -219,6 +220,7 @@ jest.mock('../../components/alerts_table/timeline_actions/use_add_bulk_to_timeli jest.mock('../../../common/components/visualization_actions/lens_embeddable'); jest.mock('../../../common/components/page/use_refetch_by_session'); +jest.mock('../../../common/hooks/use_upselling'); describe('DetectionEnginePageComponent', () => { beforeAll(() => { @@ -239,6 +241,7 @@ describe('DetectionEnginePageComponent', () => { (FilterGroup as jest.Mock).mockImplementation(() => { return ; }); + (useUpsellingMessage as jest.Mock).mockReturnValue('Go for Platinum!'); }); beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 8a8b269abf4ed..2127e0c4f26a7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -31,6 +31,9 @@ import { tableDefaults, TableId, } from '@kbn/securitysolution-data-table'; +import { isEqual } from 'lodash'; +import { FilterByAssigneesPopover } from '../../../common/components/filter_group/filter_by_assignees'; +import type { AssigneesIdsSelection } from '../../../common/components/assignees/types'; import { ALERTS_TABLE_REGISTRY_CONFIG_IDS } from '../../../../common/constants'; import { useDataTableFilters } from '../../../common/hooks/use_data_table_filters'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; @@ -62,6 +65,7 @@ import { showGlobalFilters, } from '../../../timelines/components/timeline/helpers'; import { + buildAlertAssigneesFilter, buildAlertStatusFilter, buildShowBuildingBlockFilter, buildThreatMatchFilter, @@ -135,6 +139,16 @@ const DetectionEnginePageComponent: React.FC = ({ const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); + const [assignees, setAssignees] = useState([]); + const handleSelectedAssignees = useCallback( + (newAssignees: AssigneesIdsSelection[]) => { + if (!isEqual(newAssignees, assignees)) { + setAssignees(newAssignees); + } + }, + [assignees] + ); + const arePageFiltersEnabled = useIsExperimentalFeatureEnabled('alertsPageFiltersEnabled'); // when arePageFiltersEnabled === false @@ -176,8 +190,9 @@ const DetectionEnginePageComponent: React.FC = ({ ...filters, ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), + ...buildAlertAssigneesFilter(assignees), ]; - }, [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, filters]); + }, [assignees, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, filters]); const alertPageFilters = useMemo(() => { if (arePageFiltersEnabled) { @@ -247,8 +262,9 @@ const DetectionEnginePageComponent: React.FC = ({ ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ...(alertPageFilters ?? []), + ...buildAlertAssigneesFilter(assignees), ], - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, alertPageFilters] + [assignees, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, alertPageFilters] ); const { signalIndexNeedsInit, pollForSignalIndex } = useSignalHelpers(); @@ -363,16 +379,16 @@ const DetectionEnginePageComponent: React.FC = ({ /> ), [ - topLevelFilters, arePageFiltersEnabled, - statusFilter, + from, onFilterGroupChangedCallback, pageFiltersUpdateHandler, - showUpdating, - from, query, + showUpdating, + statusFilter, timelinesUi, to, + topLevelFilters, updatedAt, ] ); @@ -448,14 +464,24 @@ const DetectionEnginePageComponent: React.FC = ({ > - - {i18n.BUTTON_MANAGE_RULES} - + + + + + + + {i18n.BUTTON_MANAGE_RULES} + + + diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx new file mode 100644 index 0000000000000..0753c6f613cb8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; + +import { ASSIGNEES_ADD_BUTTON_TEST_ID, ASSIGNEES_TITLE_TEST_ID } from './test_ids'; +import { Assignees } from './assignees'; + +import { useGetCurrentUserProfile } from '../../../../common/components/user_profiles/use_get_current_user_profile'; +import { useBulkGetUserProfiles } from '../../../../common/components/user_profiles/use_bulk_get_user_profiles'; +import { useSuggestUsers } from '../../../../common/components/user_profiles/use_suggest_users'; +import type { SetAlertAssigneesFunc } from '../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees'; +import { useSetAlertAssignees } from '../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees'; +import { TestProviders } from '../../../../common/mock'; +import { ASSIGNEES_APPLY_BUTTON_TEST_ID } from '../../../../common/components/assignees/test_ids'; +import { useLicense } from '../../../../common/hooks/use_license'; +import { useUpsellingMessage } from '../../../../common/hooks/use_upselling'; +import { + USERS_AVATARS_COUNT_BADGE_TEST_ID, + USERS_AVATARS_PANEL_TEST_ID, + USER_AVATAR_ITEM_TEST_ID, +} from '../../../../common/components/user_profiles/test_ids'; +import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'; + +jest.mock('../../../../common/components/user_profiles/use_get_current_user_profile'); +jest.mock('../../../../common/components/user_profiles/use_bulk_get_user_profiles'); +jest.mock('../../../../common/components/user_profiles/use_suggest_users'); +jest.mock('../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees'); +jest.mock('../../../../common/hooks/use_license'); +jest.mock('../../../../common/hooks/use_upselling'); +jest.mock('../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'); + +const mockUserProfiles = [ + { uid: 'user-id-1', enabled: true, user: { username: 'user1', full_name: 'User 1' }, data: {} }, + { uid: 'user-id-2', enabled: true, user: { username: 'user2', full_name: 'User 2' }, data: {} }, + { uid: 'user-id-3', enabled: true, user: { username: 'user3', full_name: 'User 3' }, data: {} }, +]; + +const renderAssignees = ( + eventId = 'event-1', + alertAssignees = ['user-id-1'], + onAssigneesUpdated = jest.fn() +) => { + const assignedProfiles = mockUserProfiles.filter((user) => alertAssignees.includes(user.uid)); + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: assignedProfiles, + }); + return render( + + + + ); +}; + +describe('', () => { + let setAlertAssigneesMock: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + (useGetCurrentUserProfile as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles[0], + }); + (useSuggestUsers as jest.Mock).mockReturnValue({ + isLoading: false, + data: mockUserProfiles, + }); + (useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: true }); + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => true }); + (useUpsellingMessage as jest.Mock).mockReturnValue('Go for Platinum!'); + + setAlertAssigneesMock = jest.fn().mockReturnValue(Promise.resolve()); + (useSetAlertAssignees as jest.Mock).mockReturnValue(setAlertAssigneesMock); + }); + + it('should render component', () => { + const { getByTestId } = renderAssignees(); + + expect(getByTestId(ASSIGNEES_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(USERS_AVATARS_PANEL_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).not.toBeDisabled(); + }); + + it('should render assignees avatars', () => { + const assignees = ['user-id-1', 'user-id-2']; + const { getByTestId, queryByTestId } = renderAssignees('test-event', assignees); + + expect(getByTestId(USER_AVATAR_ITEM_TEST_ID('user1'))).toBeInTheDocument(); + expect(getByTestId(USER_AVATAR_ITEM_TEST_ID('user2'))).toBeInTheDocument(); + + expect(queryByTestId(USERS_AVATARS_COUNT_BADGE_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render badge with assignees count in case there are more than two users assigned to an alert', () => { + const assignees = ['user-id-1', 'user-id-2', 'user-id-3']; + const { getByTestId, queryByTestId } = renderAssignees('test-event', assignees); + + const assigneesCountBadge = getByTestId(USERS_AVATARS_COUNT_BADGE_TEST_ID); + expect(assigneesCountBadge).toBeInTheDocument(); + expect(assigneesCountBadge).toHaveTextContent(`${assignees.length}`); + + expect(queryByTestId(USER_AVATAR_ITEM_TEST_ID('user1'))).not.toBeInTheDocument(); + expect(queryByTestId(USER_AVATAR_ITEM_TEST_ID('user2'))).not.toBeInTheDocument(); + expect(queryByTestId(USER_AVATAR_ITEM_TEST_ID('user3'))).not.toBeInTheDocument(); + }); + + it('should call assignees update functionality with the right arguments', () => { + const assignedProfiles = [mockUserProfiles[0], mockUserProfiles[1]]; + (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ + isLoading: false, + data: assignedProfiles, + }); + + const assignees = assignedProfiles.map((assignee) => assignee.uid); + const { getByTestId, getByText } = renderAssignees('test-event', assignees); + + // Update assignees + getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID).click(); + getByText('User 1').click(); + getByText('User 3').click(); + + // Apply assignees + getByTestId(ASSIGNEES_APPLY_BUTTON_TEST_ID).click(); + + expect(setAlertAssigneesMock).toHaveBeenCalledWith( + { + add: ['user-id-3'], + remove: ['user-id-1'], + }, + ['test-event'], + expect.anything(), + expect.anything() + ); + }); + + it('should render add assignees button as disabled if user has readonly priviliges', () => { + (useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: false }); + + const assignees = ['user-id-1', 'user-id-2']; + const { getByTestId } = renderAssignees('test-event', assignees); + + expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeDisabled(); + }); + + it('should render add assignees button as disabled within Basic license', () => { + (useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => false }); + + const assignees = ['user-id-1', 'user-id-2']; + const { getByTestId } = renderAssignees('test-event', assignees); + + expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeDisabled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx new file mode 100644 index 0000000000000..7544388a62f96 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx @@ -0,0 +1,159 @@ +/* + * 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 { noop } from 'lodash'; +import type { FC } from 'react'; +import React, { memo, useCallback, useMemo, useState } from 'react'; + +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useUpsellingMessage } from '../../../../common/hooks/use_upselling'; +import { useLicense } from '../../../../common/hooks/use_license'; +import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'; +import { useBulkGetUserProfiles } from '../../../../common/components/user_profiles/use_bulk_get_user_profiles'; +import { removeNoAssigneesSelection } from '../../../../common/components/assignees/utils'; +import type { AssigneesIdsSelection } from '../../../../common/components/assignees/types'; +import { AssigneesPopover } from '../../../../common/components/assignees/assignees_popover'; +import { UsersAvatarsPanel } from '../../../../common/components/user_profiles/users_avatars_panel'; +import { useSetAlertAssignees } from '../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees'; +import { + ASSIGNEES_ADD_BUTTON_TEST_ID, + ASSIGNEES_HEADER_TEST_ID, + ASSIGNEES_TITLE_TEST_ID, +} from './test_ids'; + +const UpdateAssigneesButton: FC<{ + isDisabled: boolean; + toolTipMessage: string; + togglePopover: () => void; +}> = memo(({ togglePopover, isDisabled, toolTipMessage }) => ( + + + +)); +UpdateAssigneesButton.displayName = 'UpdateAssigneesButton'; + +export interface AssigneesProps { + /** + * Id of the document + */ + eventId: string; + + /** + * The array of ids of the users assigned to the alert + */ + assignedUserIds: string[]; + + /** + * Callback to handle the successful assignees update + */ + onAssigneesUpdated?: () => void; +} + +/** + * Document assignees details displayed in flyout right section header + */ +export const Assignees: FC = memo( + ({ eventId, assignedUserIds, onAssigneesUpdated }) => { + const isPlatinumPlus = useLicense().isPlatinumPlus(); + const upsellingMessage = useUpsellingMessage('alert_assignments'); + + const { hasIndexWrite } = useAlertsPrivileges(); + const setAlertAssignees = useSetAlertAssignees(); + + const uids = useMemo(() => new Set(assignedUserIds), [assignedUserIds]); + const { data: assignedUsers } = useBulkGetUserProfiles({ uids }); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onSuccess = useCallback(() => { + if (onAssigneesUpdated) onAssigneesUpdated(); + }, [onAssigneesUpdated]); + + const togglePopover = useCallback(() => { + setIsPopoverOpen((value) => !value); + }, []); + + const onAssigneesApply = useCallback( + async (assigneesIds: AssigneesIdsSelection[]) => { + setIsPopoverOpen(false); + if (setAlertAssignees) { + const updatedIds = removeNoAssigneesSelection(assigneesIds); + const assigneesToAddArray = updatedIds.filter((uid) => !assignedUserIds.includes(uid)); + const assigneesToRemoveArray = assignedUserIds.filter((uid) => !updatedIds.includes(uid)); + + const assigneesToUpdate = { + add: assigneesToAddArray, + remove: assigneesToRemoveArray, + }; + + await setAlertAssignees(assigneesToUpdate, [eventId], onSuccess, noop); + } + }, + [assignedUserIds, eventId, onSuccess, setAlertAssignees] + ); + + return ( + + + +

+ +

+
+
+ {assignedUsers && ( + + + + )} + + + } + isPopoverOpen={isPopoverOpen} + closePopover={togglePopover} + onAssigneesApply={onAssigneesApply} + /> + +
+ ); + } +); + +Assignees.displayName = 'Assignees'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx index c8dfa6e847412..0d8f581626736 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx @@ -6,26 +6,36 @@ */ import type { FC } from 'react'; -import React, { memo, useMemo } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isEmpty } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; +import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; import { DocumentStatus } from './status'; import { DocumentSeverity } from './severity'; import { RiskScore } from './risk_score'; +import { useRefetchByScope } from '../../../../timelines/components/side_panel/event_details/flyout/use_refetch_by_scope'; import { useBasicDataFromDetailsData } from '../../../../timelines/components/side_panel/event_details/helpers'; import { useRightPanelContext } from '../context'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; import { RenderRuleName } from '../../../../timelines/components/timeline/body/renderers/formatted_field_helpers'; import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; +import { Assignees } from './assignees'; import { FlyoutTitle } from '../../../shared/components/flyout_title'; /** * Document details flyout right section header */ export const HeaderTitle: FC = memo(() => { - const { dataFormattedForFieldBrowser, eventId, scopeId, isPreview } = useRightPanelContext(); + const { + dataFormattedForFieldBrowser, + eventId, + scopeId, + isPreview, + refetchFlyoutData, + getFieldsData, + } = useRightPanelContext(); const { isAlert, ruleName, timestamp, ruleId } = useBasicDataFromDetailsData( dataFormattedForFieldBrowser ); @@ -72,6 +82,16 @@ export const HeaderTitle: FC = memo(() => { ); + const { refetch } = useRefetchByScope({ scopeId }); + const alertAssignees = useMemo( + () => (getFieldsData(ALERT_WORKFLOW_ASSIGNEE_IDS) as string[]) ?? [], + [getFieldsData] + ); + const onAssigneesUpdated = useCallback(() => { + refetch(); + refetchFlyoutData(); + }, [refetch, refetchFlyoutData]); + return ( <> @@ -91,6 +111,15 @@ export const HeaderTitle: FC = memo(() => { + {isAlert && !isPreview && ( + + + + )}
); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts index 0cbde8fa94e1a..5b176a34014ab 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts @@ -19,6 +19,10 @@ export const RISK_SCORE_VALUE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}RiskScoreValue` export const SHARE_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}ShareButton` as const; export const CHAT_BUTTON_TEST_ID = 'newChatById' as const; +export const ASSIGNEES_HEADER_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AssigneesHeader` as const; +export const ASSIGNEES_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AssigneesTitle` as const; +export const ASSIGNEES_ADD_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AssigneesAddButton` as const; + /* About section */ export const ABOUT_SECTION_TEST_ID = `${PREFIX}AboutSection` as const; diff --git a/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx b/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx index 2da08344a70a0..ebd09f7839fb4 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx @@ -59,14 +59,14 @@ const StyledGraphControlsColumn = styled.div` } `; +const COLUMN_WIDTH = ['fit-content(10em)', 'auto']; + const StyledEuiDescriptionListTitle = styled(EuiDescriptionListTitle)` text-transform: uppercase; - max-width: 25%; `; const StyledEuiDescriptionListDescription = styled(EuiDescriptionListDescription)` - min-width: 75%; - width: 75%; + lineheight: '2.2em'; // lineHeight to align center vertically `; const StyledEuiButtonIcon = styled(EuiButtonIcon)` @@ -386,52 +386,35 @@ const SchemaInformation = ({ <> - + {i18n.translate('xpack.securitySolution.resolver.graphControls.schemaSource', { defaultMessage: 'source', })} - + {sourceAndSchema?.dataSource ?? unknownSchemaValue} - - + + {i18n.translate('xpack.securitySolution.resolver.graphControls.schemaID', { defaultMessage: 'id', })} - + {sourceAndSchema?.schema.id ?? unknownSchemaValue} - - + + {i18n.translate('xpack.securitySolution.resolver.graphControls.schemaEdge', { defaultMessage: 'edge', })} - + {sourceAndSchema?.schema.parent ?? unknownSchemaValue} - +
@@ -493,14 +476,12 @@ const NodeLegend = ({ <> - + - + {i18n.translate( 'xpack.securitySolution.resolver.graphControls.runningProcessCube', @@ -521,10 +499,7 @@ const NodeLegend = ({ )} - + - + {i18n.translate( 'xpack.securitySolution.resolver.graphControls.terminatedProcessCube', @@ -545,10 +517,7 @@ const NodeLegend = ({ )} - + - + {i18n.translate( 'xpack.securitySolution.resolver.graphControls.currentlyLoadingCube', @@ -569,10 +535,7 @@ const NodeLegend = ({ )} - + - + {i18n.translate('xpack.securitySolution.resolver.graphControls.errorCube', { defaultMessage: 'Error Process', diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx index 64c94f76f86a1..07ef565865d82 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx @@ -11,18 +11,11 @@ import React, { memo, useMemo, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { EuiBreadcrumb } from '@elastic/eui'; -import { - EuiSpacer, - EuiText, - EuiDescriptionList, - EuiHorizontalRule, - EuiTextColor, - EuiTitle, -} from '@elastic/eui'; +import { EuiSpacer, EuiText, EuiHorizontalRule, EuiTextColor, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { StyledPanel } from '../styles'; -import { BoldCode, StyledTime } from './styles'; +import { StyledDescriptionList, BoldCode, StyledTime } from './styles'; import { GeneratedText } from '../generated_text'; import { CopyablePanelField } from './copyable_panel_field'; import { Breadcrumbs } from './breadcrumbs'; @@ -329,16 +322,6 @@ function EventDetailBreadcrumbs({ return ; } -const StyledDescriptionList = memo(styled(EuiDescriptionList)` - .euiDescriptionList__title { - word-break: normal; - } - .euiDescriptionList__title, - .euiDescriptionList__description { - overflow-wrap: break-word; - } -`); - // Also prevents horizontal scrollbars on long descriptive names const StyledDescriptiveName = memo(styled(EuiText)` padding-right: 1em; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx index ab762ec8a4542..c258860e4f35c 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx @@ -35,6 +35,8 @@ const StyledCubeForProcess = styled(CubeForProcess)` position: relative; `; +const COLUMN_WIDTH = ['fit-content(10em)', 'auto']; + const nodeDetailError = i18n.translate('xpack.securitySolution.resolver.panel.nodeDetail.Error', { defaultMessage: 'Node details were unable to be retrieved', }); @@ -249,6 +251,7 @@ const NodeDetailView = memo(function ({
+
+
+
+

+ Assignees: +

+
+
+
+
+
+
+
+ + + +
+
+
+
+
, @@ -199,7 +256,7 @@ exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should class="euiFlexItem emotion-euiFlexItem-growZero" >
+
+
+
+

+ Assignees: +

+
+
+
+
+
+
+
+ + + +
+
+
+
+
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 82d41e0eafb33..18cef99f0cb20 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -19,9 +19,13 @@ import { EuiSpacer, EuiCopy, } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; +import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; +import { TableId } from '@kbn/securitysolution-data-table'; +import type { GetFieldsData } from '../../../../common/hooks/use_get_fields_data'; +import { Assignees } from '../../../../flyout/document_details/right/components/assignees'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; import type { TimelineTabs } from '../../../../../common/types/timeline'; import type { BrowserFields } from '../../../../common/containers/source'; @@ -34,6 +38,7 @@ import { } from '../../../../common/components/event_details/translations'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; import { useGetAlertDetailsFlyoutLink } from './use_get_alert_details_flyout_link'; +import { useRefetchByScope } from './flyout/use_refetch_by_scope'; export type HandleOnEventClosed = () => void; interface Props { @@ -61,6 +66,9 @@ interface ExpandableEventTitleProps { ruleName?: string; timestamp: string; handleOnEventClosed?: HandleOnEventClosed; + scopeId: string; + refetchFlyoutData: () => Promise; + getFieldsData: GetFieldsData; } const StyledEuiFlexGroup = styled(EuiFlexGroup)` @@ -89,6 +97,9 @@ export const ExpandableEventTitle = React.memo( promptContextId, ruleName, timestamp, + scopeId, + refetchFlyoutData, + getFieldsData, }) => { const { hasAssistantPrivilege } = useAssistantAvailability(); const alertDetailsLink = useGetAlertDetailsFlyoutLink({ @@ -97,6 +108,16 @@ export const ExpandableEventTitle = React.memo( timestamp, }); + const { refetch } = useRefetchByScope({ scopeId }); + const alertAssignees = useMemo( + () => (getFieldsData(ALERT_WORKFLOW_ASSIGNEE_IDS) as string[]) ?? [], + [getFieldsData] + ); + const onAssigneesUpdated = useCallback(() => { + refetch(); + refetchFlyoutData(); + }, [refetch, refetchFlyoutData]); + return ( @@ -115,7 +136,7 @@ export const ExpandableEventTitle = React.memo( )} - + {handleOnEventClosed && ( ( )} + {scopeId !== TableId.rulePreview && ( + + + + )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx index 15d6b2040234a..97a7e9196ae4d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx @@ -8,8 +8,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { find } from 'lodash/fp'; -import type { ConnectedProps } from 'react-redux'; -import { connect } from 'react-redux'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { isActiveTimeline } from '../../../../../helpers'; import { TakeActionDropdown } from '../../../../../detections/components/take_action_dropdown'; @@ -20,9 +18,9 @@ import { EventFiltersFlyout } from '../../../../../management/pages/event_filter import { useEventFilterModal } from '../../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; import { getFieldValue } from '../../../../../detections/components/host_isolation/helpers'; import type { Status } from '../../../../../../common/api/detection_engine'; -import type { inputsModel, State } from '../../../../../common/store'; -import { inputsSelectors } from '../../../../../common/store'; import { OsqueryFlyout } from '../../../../../detections/components/osquery/osquery_flyout'; +import { useRefetchByScope } from './use_refetch_by_scope'; + interface FlyoutFooterProps { detailsData: TimelineEventsDetailsItem[] | null; detailsEcsData: Ecs | null; @@ -43,176 +41,142 @@ interface AddExceptionModalWrapperData { ruleName: string; } -// eslint-disable-next-line react/display-name -export const FlyoutFooterComponent = React.memo( - ({ - detailsData, - detailsEcsData, - handleOnEventClosed, - isHostIsolationPanelOpen, - isReadOnly, - loadingEventDetails, - onAddIsolationStatusClick, - scopeId, - globalQuery, - timelineQuery, - refetchFlyoutData, - }: FlyoutFooterProps & PropsFromRedux) => { - const alertId = detailsEcsData?.kibana?.alert ? detailsEcsData?._id : null; - const ruleIndexRaw = useMemo( - () => - find({ category: 'signal', field: 'signal.rule.index' }, detailsData)?.values ?? - find({ category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, detailsData) - ?.values, - [detailsData] - ); - const ruleIndex = useMemo( - (): string[] | undefined => (Array.isArray(ruleIndexRaw) ? ruleIndexRaw : undefined), - [ruleIndexRaw] - ); - const ruleDataViewIdRaw = useMemo( - () => - find({ category: 'signal', field: 'signal.rule.data_view_id' }, detailsData)?.values ?? - find( - { category: 'kibana', field: 'kibana.alert.rule.parameters.data_view_id' }, - detailsData - )?.values, - [detailsData] - ); - const ruleDataViewId = useMemo( - (): string | undefined => - Array.isArray(ruleDataViewIdRaw) ? ruleDataViewIdRaw[0] : undefined, - [ruleDataViewIdRaw] - ); - - const addExceptionModalWrapperData = useMemo( - () => - [ - { category: 'signal', field: 'signal.rule.id', name: 'ruleId' }, - { category: 'signal', field: 'signal.rule.rule_id', name: 'ruleRuleId' }, - { category: 'signal', field: 'signal.rule.name', name: 'ruleName' }, - { category: 'signal', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, - { category: '_id', field: '_id', name: 'eventId' }, - ].reduce( - (acc, curr) => ({ - ...acc, - [curr.name]: getFieldValue({ category: curr.category, field: curr.field }, detailsData), - }), - {} as AddExceptionModalWrapperData - ), - [detailsData] - ); +export const FlyoutFooterComponent = ({ + detailsData, + detailsEcsData, + handleOnEventClosed, + isHostIsolationPanelOpen, + isReadOnly, + loadingEventDetails, + onAddIsolationStatusClick, + scopeId, + refetchFlyoutData, +}: FlyoutFooterProps) => { + const alertId = detailsEcsData?.kibana?.alert ? detailsEcsData?._id : null; + const ruleIndexRaw = useMemo( + () => + find({ category: 'signal', field: 'signal.rule.index' }, detailsData)?.values ?? + find({ category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, detailsData) + ?.values, + [detailsData] + ); + const ruleIndex = useMemo( + (): string[] | undefined => (Array.isArray(ruleIndexRaw) ? ruleIndexRaw : undefined), + [ruleIndexRaw] + ); + const ruleDataViewIdRaw = useMemo( + () => + find({ category: 'signal', field: 'signal.rule.data_view_id' }, detailsData)?.values ?? + find({ category: 'kibana', field: 'kibana.alert.rule.parameters.data_view_id' }, detailsData) + ?.values, + [detailsData] + ); + const ruleDataViewId = useMemo( + (): string | undefined => (Array.isArray(ruleDataViewIdRaw) ? ruleDataViewIdRaw[0] : undefined), + [ruleDataViewIdRaw] + ); - const refetchQuery = (newQueries: inputsModel.GlobalQuery[]) => { - newQueries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); - }; + const addExceptionModalWrapperData = useMemo( + () => + [ + { category: 'signal', field: 'signal.rule.id', name: 'ruleId' }, + { category: 'signal', field: 'signal.rule.rule_id', name: 'ruleRuleId' }, + { category: 'signal', field: 'signal.rule.name', name: 'ruleName' }, + { category: 'signal', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, + { category: '_id', field: '_id', name: 'eventId' }, + ].reduce( + (acc, curr) => ({ + ...acc, + [curr.name]: getFieldValue({ category: curr.category, field: curr.field }, detailsData), + }), + {} as AddExceptionModalWrapperData + ), + [detailsData] + ); - const refetchAll = useCallback(() => { - if (isActiveTimeline(scopeId)) { - refetchQuery([timelineQuery]); - } else { - refetchQuery(globalQuery); - } - }, [scopeId, timelineQuery, globalQuery]); + const { refetch: refetchAll } = useRefetchByScope({ scopeId }); - const { - exceptionFlyoutType, - openAddExceptionFlyout, - onAddExceptionTypeClick, - onAddExceptionCancel, - onAddExceptionConfirm, - } = useExceptionFlyout({ - refetch: refetchAll, - isActiveTimelines: isActiveTimeline(scopeId), - }); - const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = - useEventFilterModal(); + const { + exceptionFlyoutType, + openAddExceptionFlyout, + onAddExceptionTypeClick, + onAddExceptionCancel, + onAddExceptionConfirm, + } = useExceptionFlyout({ + refetch: refetchAll, + isActiveTimelines: isActiveTimeline(scopeId), + }); + const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = + useEventFilterModal(); - const [isOsqueryFlyoutOpenWithAgentId, setOsqueryFlyoutOpenWithAgentId] = useState< - null | string - >(null); + const [isOsqueryFlyoutOpenWithAgentId, setOsqueryFlyoutOpenWithAgentId] = useState( + null + ); - const closeOsqueryFlyout = useCallback(() => { - setOsqueryFlyoutOpenWithAgentId(null); - }, [setOsqueryFlyoutOpenWithAgentId]); + const closeOsqueryFlyout = useCallback(() => { + setOsqueryFlyoutOpenWithAgentId(null); + }, [setOsqueryFlyoutOpenWithAgentId]); - if (isReadOnly) { - return null; - } + if (isReadOnly) { + return null; + } - return ( - <> - - - - {detailsEcsData && ( - - )} - - - - {/* This is still wrong to do render flyout/modal inside of the flyout + return ( + <> + + + + {detailsEcsData && ( + + )} + + + + {/* This is still wrong to do render flyout/modal inside of the flyout We need to completely refactor the EventDetails component to be correct */} - {openAddExceptionFlyout && - addExceptionModalWrapperData.ruleId != null && - addExceptionModalWrapperData.ruleRuleId != null && - addExceptionModalWrapperData.eventId != null && ( - - )} - {isAddEventFilterModalOpen && detailsEcsData != null && ( - - )} - {isOsqueryFlyoutOpenWithAgentId && detailsEcsData != null && ( - )} - - ); - } -); - -const makeMapStateToProps = () => { - const getGlobalQueries = inputsSelectors.globalQuery(); - const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); - const mapStateToProps = (state: State, { scopeId }: FlyoutFooterProps) => { - return { - globalQuery: getGlobalQueries(state), - timelineQuery: getTimelineQuery(state, scopeId), - }; - }; - return mapStateToProps; + {isAddEventFilterModalOpen && detailsEcsData != null && ( + + )} + {isOsqueryFlyoutOpenWithAgentId && detailsEcsData != null && ( + + )} + + ); }; -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const FlyoutFooter = connector(React.memo(FlyoutFooterComponent)); +export const FlyoutFooter = React.memo(FlyoutFooterComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx index 8b3d50d849c4b..d5df4304a0894 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx @@ -8,6 +8,7 @@ import { EuiFlyoutHeader } from '@elastic/eui'; import React from 'react'; +import type { GetFieldsData } from '../../../../../common/hooks/use_get_fields_data'; import { ExpandableEventTitle } from '../expandable_event'; import { BackToAlertDetailsLink } from './back_to_alert_details_link'; @@ -22,6 +23,9 @@ interface FlyoutHeaderComponentProps { ruleName: string; showAlertDetails: () => void; timestamp: string; + scopeId: string; + refetchFlyoutData: () => Promise; + getFieldsData: GetFieldsData; } const FlyoutHeaderContentComponent = ({ @@ -35,6 +39,9 @@ const FlyoutHeaderContentComponent = ({ ruleName, showAlertDetails, timestamp, + scopeId, + refetchFlyoutData, + getFieldsData, }: FlyoutHeaderComponentProps) => { return ( <> @@ -49,6 +56,9 @@ const FlyoutHeaderContentComponent = ({ promptContextId={promptContextId} ruleName={ruleName} timestamp={timestamp} + scopeId={scopeId} + refetchFlyoutData={refetchFlyoutData} + getFieldsData={getFieldsData} /> )} @@ -67,6 +77,9 @@ const FlyoutHeaderComponent = ({ ruleName, showAlertDetails, timestamp, + scopeId, + refetchFlyoutData, + getFieldsData, }: FlyoutHeaderComponentProps) => { return ( @@ -81,6 +94,9 @@ const FlyoutHeaderComponent = ({ ruleName={ruleName} showAlertDetails={showAlertDetails} timestamp={timestamp} + scopeId={scopeId} + refetchFlyoutData={refetchFlyoutData} + getFieldsData={getFieldsData} /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_refetch_by_scope.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_refetch_by_scope.tsx new file mode 100644 index 0000000000000..efb7c19ba687f --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_refetch_by_scope.tsx @@ -0,0 +1,45 @@ +/* + * 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 { useCallback } from 'react'; +import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; +import { isActiveTimeline } from '../../../../../helpers'; +import type { inputsModel } from '../../../../../common/store'; +import { inputsSelectors } from '../../../../../common/store'; + +export interface UseRefetchScopeQueryParams { + /** + * Scope ID + */ + scopeId: string; +} + +/** + * Hook to refetch data within specified scope + */ +export const useRefetchByScope = ({ scopeId }: UseRefetchScopeQueryParams) => { + const getGlobalQueries = inputsSelectors.globalQuery(); + const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); + const { globalQuery, timelineQuery } = useDeepEqualSelector((state) => ({ + globalQuery: getGlobalQueries(state), + timelineQuery: getTimelineQuery(state, scopeId), + })); + + const refetchQuery = (newQueries: inputsModel.GlobalQuery[]) => { + newQueries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); + }; + + const refetchAll = useCallback(() => { + if (isActiveTimeline(scopeId)) { + refetchQuery([timelineQuery]); + } else { + refetchQuery(globalQuery); + } + }, [scopeId, timelineQuery, globalQuery]); + + return { refetch: refetchAll }; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx index 508a5caa590f4..3f7702d490e9d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx @@ -22,6 +22,7 @@ import { DEFAULT_PREVIEW_INDEX, ASSISTANT_FEATURE_ID, } from '../../../../../common/constants'; +import { useUpsellingMessage } from '../../../../common/hooks/use_upselling'; const ecsData: Ecs = { _id: '1', @@ -69,6 +70,18 @@ jest.mock( } ); +jest.mock('../../../../common/components/user_profiles/use_bulk_get_user_profiles', () => { + return { + useBulkGetUserProfiles: jest.fn().mockReturnValue({ isLoading: false, data: [] }), + }; +}); + +jest.mock('../../../../common/components/user_profiles/use_suggest_users', () => { + return { + useSuggestUsers: jest.fn().mockReturnValue({ isLoading: false, data: [] }), + }; +}); + jest.mock('../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), })); @@ -112,6 +125,7 @@ jest.mock('../../../../explore/containers/risk_score', () => { }), }; }); +jest.mock('../../../../common/hooks/use_upselling'); const defaultProps = { scopeId: TimelineId.test, @@ -167,6 +181,7 @@ describe('event details panel component', () => { }, }, }); + (useUpsellingMessage as jest.Mock).mockReturnValue('Go for Platinum!'); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index a2a45e46a07b2..20b6341db6d91 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -13,6 +13,7 @@ import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; import type { EntityType } from '@kbn/timelines-plugin/common'; +import { useGetFieldsData } from '../../../../common/hooks/use_get_fields_data'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; import { getRawData } from '../../../../assistant/helpers'; import type { BrowserFields } from '../../../../common/containers/source'; @@ -94,6 +95,7 @@ const EventDetailsPanelComponent: React.FC = ({ skip: !expandedEvent.eventId, } ); + const getFieldsData = useGetFieldsData(rawEventData?.fields); const { isolateAction, @@ -137,6 +139,9 @@ const EventDetailsPanelComponent: React.FC = ({ showAlertDetails={showAlertDetails} timestamp={timestamp} promptContextId={promptContextId} + scopeId={scopeId} + refetchFlyoutData={refetchFlyoutData} + getFieldsData={getFieldsData} /> ) : ( = ({ timestamp={timestamp} handleOnEventClosed={handleOnEventClosed} promptContextId={promptContextId} + scopeId={scopeId} + refetchFlyoutData={refetchFlyoutData} + getFieldsData={getFieldsData} /> ), [ @@ -161,8 +169,11 @@ const EventDetailsPanelComponent: React.FC = ({ ruleName, showAlertDetails, timestamp, - handleOnEventClosed, promptContextId, + handleOnEventClosed, + scopeId, + refetchFlyoutData, + getFieldsData, ] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx index 8895d1307c89b..5e62b7e03bac3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx @@ -30,6 +30,18 @@ jest.mock('../../../common/containers/use_search_strategy', () => ({ useSearchStrategy: jest.fn(), })); +jest.mock('../../../common/components/user_profiles/use_bulk_get_user_profiles', () => { + return { + useBulkGetUserProfiles: jest.fn().mockReturnValue({ isLoading: false, data: [] }), + }; +}); + +jest.mock('../../../common/components/user_profiles/use_suggest_users', () => { + return { + useSuggestUsers: jest.fn().mockReturnValue({ isLoading: false, data: [] }), + }; +}); + jest.mock('../../../assistant/use_assistant_availability'); const mockUseLocation = jest.fn().mockReturnValue({ pathname: '/test', search: '?' }); jest.mock('react-router-dom', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts index a90aef7224a98..9770f2e6f5b67 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts @@ -11,9 +11,14 @@ import type { Filter } from '@kbn/es-query'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { ColumnHeaderOptions, RowRenderer } from '../../../../../../common/types'; import type { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; +import type { RenderCellValueContext } from '../../../../../detections/configurations/security_solution_detections/fetch_page_context'; export interface ColumnRenderer { - isInstance: (columnName: string, data: TimelineNonEcsData[]) => boolean; + isInstance: ( + columnName: string, + data: TimelineNonEcsData[], + context?: RenderCellValueContext + ) => boolean; renderColumn: ({ className, columnName, @@ -28,6 +33,7 @@ export interface ColumnRenderer { truncate, values, key, + context, }: { asPlainText?: boolean; className?: string; @@ -44,5 +50,6 @@ export interface ColumnRenderer { truncate?: boolean; values: string[] | null | undefined; key?: string; + context?: RenderCellValueContext; }) => React.ReactNode; } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx index 9308204e69318..4c3c62b5a61f8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx @@ -17,6 +17,7 @@ export const REFERENCE_URL_FIELD_NAME = 'reference.url'; export const EVENT_URL_FIELD_NAME = 'event.url'; export const SIGNAL_RULE_NAME_FIELD_NAME = 'kibana.alert.rule.name'; export const SIGNAL_STATUS_FIELD_NAME = 'kibana.alert.workflow_status'; +export const SIGNAL_ASSIGNEE_IDS_FIELD_NAME = 'kibana.alert.workflow_assignee_ids'; export const AGENT_STATUS_FIELD_NAME = 'agent.status'; export const QUARANTINED_PATH_FIELD_NAME = 'quarantined.path'; export const REASON_FIELD_NAME = 'kibana.alert.reason'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.ts index f94c1c6105533..c6cf20dfcff84 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { RenderCellValueContext } from '../../../../../detections/configurations/security_solution_detections/fetch_page_context'; import type { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; import type { ColumnRenderer } from './column_renderer'; @@ -15,10 +16,11 @@ const unhandledColumnRenderer = (): never => { export const getColumnRenderer = ( columnName: string, columnRenderers: ColumnRenderer[], - data: TimelineNonEcsData[] + data: TimelineNonEcsData[], + context?: RenderCellValueContext ): ColumnRenderer => { const renderer = columnRenderers.find((columnRenderer) => - columnRenderer.isInstance(columnName, data) + columnRenderer.isInstance(columnName, data, context) ); return renderer != null ? renderer : unhandledColumnRenderer(); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts index 21493967010fe..a8d8ee67a415b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts @@ -18,6 +18,7 @@ import { systemRowRenderers } from './system/generic_row_renderer'; import { threatMatchRowRenderer } from './cti/threat_match_row_renderer'; import { reasonColumnRenderer } from './reason_column_renderer'; import { eventSummaryColumnRenderer } from './event_summary_column_renderer'; +import { userProfileColumnRenderer } from './user_profile_renderer'; // The row renderers are order dependent and will return the first renderer // which returns true from its isInstance call. The bottom renderers which @@ -38,6 +39,7 @@ export const defaultRowRenderers: RowRenderer[] = [ export const columnRenderers: ColumnRenderer[] = [ reasonColumnRenderer, eventSummaryColumnRenderer, + userProfileColumnRenderer, plainColumnRenderer, emptyColumnRenderer, unknownColumnRenderer, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_profile_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_profile_renderer.tsx new file mode 100644 index 0000000000000..93be8036f0cea --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_profile_renderer.tsx @@ -0,0 +1,58 @@ +/* + * 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 { EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; + +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { UsersAvatarsPanel } from '../../../../../common/components/user_profiles/users_avatars_panel'; +import type { ColumnHeaderOptions, RowRenderer } from '../../../../../../common/types'; +import type { ColumnRenderer } from './column_renderer'; +import { profileUidColumns } from '../../../../../detections/configurations/security_solution_detections/fetch_page_context'; +import type { RenderCellValueContext } from '../../../../../detections/configurations/security_solution_detections/fetch_page_context'; + +export const userProfileColumnRenderer: ColumnRenderer = { + isInstance: (columnName, _, context) => profileUidColumns.includes(columnName) && !!context, + renderColumn: ({ + columnName, + ecsData, + eventId, + field, + isDetails, + isDraggable = true, + linkValues, + rowRenderers = [], + scopeId, + truncate, + values, + context, + }: { + columnName: string; + ecsData?: Ecs; + eventId: string; + field: ColumnHeaderOptions; + isDetails?: boolean; + isDraggable?: boolean; + linkValues?: string[] | null | undefined; + rowRenderers?: RowRenderer[]; + scopeId: string; + truncate?: boolean; + values: string[] | undefined | null; + context?: RenderCellValueContext; + }) => { + // Show spinner if loading profiles or if there are no fetched profiles yet + // Do not show the loading spinner if context is not provided at all + if (context?.isLoading) { + return ; + } + + const userProfiles = + values?.map((uid) => context?.profiles?.find((user) => uid === user.uid)) ?? []; + + return ; + }, +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx index 3487e2770ff45..33a47bc8c0698 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.test.tsx @@ -76,7 +76,7 @@ describe('DefaultCellRenderer', () => { ); - expect(getColumnRenderer).toBeCalledWith(header.id, columnRenderers, data); + expect(getColumnRenderer).toBeCalledWith(header.id, columnRenderers, data, undefined); }); test('if in tgrid expanded value, it invokes `renderColumn` with the expected arguments', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx index 551003923a151..9e5006267d32b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx @@ -33,6 +33,7 @@ export const DefaultCellRenderer: React.FC = ({ scopeId, truncate, asPlainText, + context, }) => { const asPlainTextDefault = useMemo(() => { return ( @@ -49,7 +50,7 @@ export const DefaultCellRenderer: React.FC = ({ : 'eui-displayInlineBlock eui-textTruncate'; return ( - {getColumnRenderer(header.id, columnRenderers, data).renderColumn({ + {getColumnRenderer(header.id, columnRenderers, data, context).renderColumn({ asPlainText: asPlainText ?? asPlainTextDefault, // we want to render value with links as plain text but keep other formatters like badge. Except rule name for non preview tables columnName: header.id, ecsData, @@ -62,6 +63,7 @@ export const DefaultCellRenderer: React.FC = ({ scopeId, truncate, values, + context, })} ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 78d8eecc109f3..2f6ea38b40b81 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -506,6 +506,11 @@ export const getSignalsMigrationStatusRequest = () => query: getSignalsMigrationStatusSchemaMock(), }); +export const getMockUserProfiles = () => [ + { uid: 'default-test-assignee-id-1', enabled: true, user: { username: 'user1' }, data: {} }, + { uid: 'default-test-assignee-id-2', enabled: true, user: { username: 'user2' }, data: {} }, +]; + /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/helpers.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/helpers.ts index a557586a008fd..41a5ddfae2be9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AlertTags } from '../../../../../common/api/detection_engine'; +import type { AlertTags, AlertAssignees } from '../../../../../common/api/detection_engine'; import * as i18n from './translations'; export const validateAlertTagsArrays = (tags: AlertTags, ids: string[]) => { @@ -20,3 +20,13 @@ export const validateAlertTagsArrays = (tags: AlertTags, ids: string[]) => { } return validationErrors; }; + +export const validateAlertAssigneesArrays = (assignees: AlertAssignees) => { + const validationErrors = []; + const { add: assigneesToAdd, remove: assigneesToRemove } = assignees; + const duplicates = assigneesToAdd.filter((assignee) => assigneesToRemove.includes(assignee)); + if (duplicates.length) { + validationErrors.push(i18n.ALERT_ASSIGNEES_VALIDATION_ERROR(JSON.stringify(duplicates))); + } + return validationErrors; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.test.ts new file mode 100644 index 0000000000000..dfc0603598a00 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.test.ts @@ -0,0 +1,120 @@ +/* + * 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 { getSetAlertAssigneesRequestMock } from '../../../../../common/api/detection_engine/alert_assignees/mocks'; +import { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '../../../../../common/constants'; +import { requestContextMock, serverMock, requestMock } from '../__mocks__'; +import { getSuccessfulSignalUpdateResponse } from '../__mocks__/request_responses'; +import { setAlertAssigneesRoute } from './set_alert_assignees_route'; + +describe('setAlertAssigneesRoute', () => { + let server: ReturnType; + let request: ReturnType; + let { context } = requestContextMock.createTools(); + + beforeEach(() => { + server = serverMock.create(); + ({ context } = requestContextMock.createTools()); + setAlertAssigneesRoute(server.router); + }); + + describe('happy path', () => { + test('returns 200 when adding/removing empty arrays of assignees', async () => { + request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + body: getSetAlertAssigneesRequestMock(['assignee-id-1'], ['assignee-id-2'], ['alert-id']), + }); + + context.core.elasticsearch.client.asCurrentUser.bulk.mockResponse({ + errors: false, + took: 0, + items: [{ update: { result: 'updated', status: 200, _index: 'test-index' } }], + }); + + const response = await server.inject(request, requestContextMock.convertContext(context)); + + expect(response.status).toEqual(200); + }); + }); + + describe('validation', () => { + test('returns 400 if duplicate assignees are in both the add and remove arrays', async () => { + request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + body: getSetAlertAssigneesRequestMock(['assignee-id-1'], ['assignee-id-1'], ['test-id']), + }); + + context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockResponse( + getSuccessfulSignalUpdateResponse() + ); + + const response = await server.inject(request, requestContextMock.convertContext(context)); + + context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockRejectedValue( + new Error('Test error') + ); + + expect(response.body).toEqual({ + message: [ + `Duplicate assignees [\"assignee-id-1\"] were found in the add and remove parameters.`, + ], + status_code: 400, + }); + }); + + test('rejects if no alert ids are provided', async () => { + request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + body: getSetAlertAssigneesRequestMock(['assignee-id-1'], ['assignee-id-2']), + }); + + const result = server.validate(request); + + expect(result.badRequest).toHaveBeenCalledWith( + 'ids: Array must contain at least 1 element(s)' + ); + }); + + test('rejects if empty string provided as an alert id', async () => { + request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + body: getSetAlertAssigneesRequestMock(['assignee-id-1'], ['assignee-id-2'], ['']), + }); + + const result = server.validate(request); + + expect(result.badRequest).toHaveBeenCalledWith( + 'ids.0: String must contain at least 1 character(s), ids.0: Invalid' + ); + }); + }); + + describe('500s', () => { + test('returns 500 if asCurrentUser throws error', async () => { + request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + body: getSetAlertAssigneesRequestMock(['assignee-id-1'], ['assignee-id-2'], ['test-id']), + }); + + context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockRejectedValue( + new Error('Test error') + ); + + const response = await server.inject(request, requestContextMock.convertContext(context)); + + expect(response.body).toEqual({ + message: 'Test error', + status_code: 500, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts new file mode 100644 index 0000000000000..f15342a36f46c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts @@ -0,0 +1,114 @@ +/* + * 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 { transformError } from '@kbn/securitysolution-es-utils'; +import { uniq } from 'lodash/fp'; +import { SetAlertAssigneesRequestBody } from '../../../../../common/api/detection_engine/alert_assignees'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { + DEFAULT_ALERTS_INDEX, + DETECTION_ENGINE_ALERT_ASSIGNEES_URL, +} from '../../../../../common/constants'; +import { buildSiemResponse } from '../utils'; +import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; +import { validateAlertAssigneesArrays } from './helpers'; + +export const setAlertAssigneesRoute = (router: SecuritySolutionPluginRouter) => { + router.versioned + .post({ + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + access: 'public', + options: { + tags: ['access:securitySolution'], + }, + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + body: buildRouteValidationWithZod(SetAlertAssigneesRequestBody), + }, + }, + }, + async (context, request, response) => { + const { assignees, ids } = request.body; + const core = await context.core; + const securitySolution = await context.securitySolution; + const esClient = core.elasticsearch.client.asCurrentUser; + const siemResponse = buildSiemResponse(response); + const validationErrors = validateAlertAssigneesArrays(assignees); + const spaceId = securitySolution?.getSpaceId() ?? 'default'; + + if (validationErrors.length) { + return siemResponse.error({ statusCode: 400, body: validationErrors }); + } + + const assigneesToAdd = uniq(assignees.add); + const assigneesToRemove = uniq(assignees.remove); + + const painlessScript = { + params: { assigneesToAdd, assigneesToRemove }, + source: `List newAssigneesArray = []; + if (ctx._source["kibana.alert.workflow_assignee_ids"] != null) { + for (assignee in ctx._source["kibana.alert.workflow_assignee_ids"]) { + if (!params.assigneesToRemove.contains(assignee)) { + newAssigneesArray.add(assignee); + } + } + for (assignee in params.assigneesToAdd) { + if (!newAssigneesArray.contains(assignee)) { + newAssigneesArray.add(assignee) + } + } + ctx._source["kibana.alert.workflow_assignee_ids"] = newAssigneesArray; + } else { + ctx._source["kibana.alert.workflow_assignee_ids"] = params.assigneesToAdd; + } + `, + lang: 'painless', + }; + + const bulkUpdateRequest = []; + for (const id of ids) { + bulkUpdateRequest.push( + { + update: { + _index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, + _id: id, + }, + }, + { + script: painlessScript, + } + ); + } + + try { + const body = await esClient.updateByQuery({ + index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, + refresh: true, + body: { + script: painlessScript, + query: { + bool: { + filter: { terms: { _id: ids } }, + }, + }, + }, + }); + return response.ok({ body }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/translations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/translations.ts index 715537fee47ab..e6b4e25e641ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/translations.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/translations.ts @@ -20,3 +20,10 @@ export const NO_IDS_VALIDATION_ERROR = i18n.translate( defaultMessage: 'No alert ids were provided', } ); + +export const ALERT_ASSIGNEES_VALIDATION_ERROR = (duplicates: string) => + i18n.translate('xpack.securitySolution.api.alertAssignees.validationError', { + values: { duplicates }, + defaultMessage: + 'Duplicate assignees { duplicates } were found in the add and remove parameters.', + }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.test.ts new file mode 100644 index 0000000000000..bd36547a5c964 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { securityMock } from '@kbn/security-plugin/server/mocks'; + +import { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '../../../../../common/constants'; +import { requestContextMock, serverMock, requestMock } from '../__mocks__'; +import { getMockUserProfiles } from '../__mocks__/request_responses'; +import { suggestUserProfilesRoute } from './suggest_user_profiles_route'; + +describe('suggestUserProfilesRoute', () => { + let server: ReturnType; + let { context } = requestContextMock.createTools(); + let mockSecurityStart: ReturnType; + let getStartServicesMock: jest.Mock; + + beforeEach(() => { + server = serverMock.create(); + ({ context } = requestContextMock.createTools()); + mockSecurityStart = securityMock.createStart(); + mockSecurityStart.userProfiles.suggest.mockResolvedValue(getMockUserProfiles()); + }); + + const buildRequest = () => { + return requestMock.create({ + method: 'get', + path: DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + body: { searchTerm: '' }, + }); + }; + + describe('normal status codes', () => { + beforeEach(() => { + getStartServicesMock = jest.fn().mockResolvedValue([{}, { security: mockSecurityStart }]); + suggestUserProfilesRoute(server.router, getStartServicesMock); + }); + + it('returns 200 when doing a normal request', async () => { + const request = buildRequest(); + const response = await server.inject(request, requestContextMock.convertContext(context)); + expect(response.status).toEqual(200); + }); + + test('returns the payload when doing a normal request', async () => { + const request = buildRequest(); + const response = await server.inject(request, requestContextMock.convertContext(context)); + const expectedBody = getMockUserProfiles(); + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedBody); + }); + + test('returns 500 if `security.userProfiles.suggest` throws error', async () => { + mockSecurityStart.userProfiles.suggest.mockRejectedValue(new Error('something went wrong')); + const request = buildRequest(); + const response = await server.inject(request, requestContextMock.convertContext(context)); + + expect(response.status).toEqual(500); + expect(response.body.message).toEqual('something went wrong'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts new file mode 100644 index 0000000000000..fcb42d2ead7e4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts @@ -0,0 +1,68 @@ +/* + * 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 { IKibanaResponse, StartServicesAccessor } from '@kbn/core/server'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL } from '../../../../../common/constants'; +import { buildSiemResponse } from '../utils'; +import type { StartPlugins } from '../../../../plugin'; +import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; +import { SuggestUserProfilesRequestQuery } from '../../../../../common/api/detection_engine/users'; + +export const suggestUserProfilesRoute = ( + router: SecuritySolutionPluginRouter, + getStartServices: StartServicesAccessor +) => { + router.versioned + .get({ + path: DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL, + access: 'public', + options: { + tags: ['access:securitySolution'], + }, + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + query: buildRouteValidationWithZod(SuggestUserProfilesRequestQuery), + }, + }, + }, + async (context, request, response): Promise> => { + const { searchTerm } = request.query; + const siemResponse = buildSiemResponse(response); + const [_, { security }] = await getStartServices(); + const securitySolution = await context.securitySolution; + const spaceId = securitySolution.getSpaceId(); + + try { + const users = await security.userProfiles.suggest({ + name: searchTerm, + dataPath: 'avatar', + requiredPrivileges: { + spaceId, + privileges: { + kibana: [security.authz.actions.login], + }, + }, + }); + + return response.ok({ body: users }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts index 6a522193558aa..8d134ad215396 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts @@ -52,6 +52,7 @@ import { ALERT_STATUS_ACTIVE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, EVENT_KIND, @@ -322,6 +323,7 @@ export const sampleAlertDocAADNoSortId = ( }, [ALERT_URL]: 'http://example.com/docID', [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], }, fields: { someKey: ['someValue'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index a9ae0d1d55696..4cf64c60de22e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -19,6 +19,7 @@ import { ALERT_STATUS_ACTIVE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, EVENT_ACTION, @@ -233,6 +234,7 @@ describe('buildAlert', () => { [ALERT_URL]: expectedAlertUrl, [ALERT_UUID]: alertUuid, [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], }; expect(alert).toEqual(expected); }); @@ -426,6 +428,7 @@ describe('buildAlert', () => { [ALERT_URL]: expectedAlertUrl, [ALERT_UUID]: alertUuid, [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], }; expect(alert).toEqual(expected); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts index 7641c71b28dbf..846c714a9c099 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -36,6 +36,7 @@ import { ALERT_STATUS_ACTIVE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, EVENT_KIND, @@ -249,6 +250,7 @@ export const buildAlert = ( [ALERT_URL]: alertUrl, [ALERT_UUID]: alertUuid, [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], ...flattenWithPrefix(ALERT_RULE_META, params.meta), // These fields don't exist in the mappings, but leaving here for now to limit changes to the alert building logic 'kibana.alert.rule.risk_score': params.riskScore, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/__mocks__/alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/__mocks__/alerts.ts index 9ffdc8eafd7f9..e19e7ad1bc0ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/__mocks__/alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/__mocks__/alerts.ts @@ -40,6 +40,7 @@ import { ALERT_STATUS_ACTIVE, ALERT_URL, ALERT_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, EVENT_KIND, @@ -96,6 +97,7 @@ export const createAlert = ( [ALERT_STATUS]: ALERT_STATUS_ACTIVE, [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], [ALERT_DEPTH]: 1, [ALERT_REASON]: 'reasonable reason', [ALERT_SEVERITY]: 'high', diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts index 4ac8f43627432..6bb003df0fa85 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts @@ -6,6 +6,7 @@ */ import type { Logger, ElasticsearchClient } from '@kbn/core/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; +import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics/asset_criticality'; import { createOrUpdateIndex } from '../utils/create_or_update_index'; import { getAssetCriticalityIndex } from '../../../../common/asset_criticality'; import { assetCriticalityFieldMap } from './configurations'; @@ -16,6 +17,15 @@ interface AssetCriticalityClientOpts { namespace: string; } +interface AssetCriticalityUpsert { + idField: AssetCriticalityRecord['id_field']; + idValue: AssetCriticalityRecord['id_value']; + criticalityLevel: AssetCriticalityRecord['criticality_level']; +} + +type AssetCriticalityIdParts = Pick; + +const createId = ({ idField, idValue }: AssetCriticalityIdParts) => `${idField}:${idValue}`; export class AssetCriticalityDataClient { constructor(private readonly options: AssetCriticalityClientOpts) {} /** @@ -27,16 +37,20 @@ export class AssetCriticalityDataClient { esClient: this.options.esClient, logger: this.options.logger, options: { - index: getAssetCriticalityIndex(this.options.namespace), + index: this.getIndex(), mappings: mappingFromFieldMap(assetCriticalityFieldMap, 'strict'), }, }); } + private getIndex() { + return getAssetCriticalityIndex(this.options.namespace); + } + public async doesIndexExist() { try { const result = await this.options.esClient.indices.exists({ - index: getAssetCriticalityIndex(this.options.namespace), + index: this.getIndex(), }); return result; } catch (e) { @@ -51,4 +65,51 @@ export class AssetCriticalityDataClient { isAssetCriticalityResourcesInstalled, }; } + + public async get(idParts: AssetCriticalityIdParts): Promise { + const id = createId(idParts); + + try { + const body = await this.options.esClient.get({ + id, + index: this.getIndex(), + }); + + return body._source; + } catch (err) { + if (err.statusCode === 404) { + return undefined; + } else { + throw err; + } + } + } + + public async upsert(record: AssetCriticalityUpsert): Promise { + const id = createId(record); + const doc = { + id_field: record.idField, + id_value: record.idValue, + criticality_level: record.criticalityLevel, + '@timestamp': new Date().toISOString(), + }; + + await this.options.esClient.update({ + id, + index: this.getIndex(), + body: { + doc, + doc_as_upsert: true, + }, + }); + + return doc; + } + + public async delete(idParts: AssetCriticalityIdParts) { + await this.options.esClient.delete({ + id: createId(idParts), + index: this.getIndex(), + }); + } } diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts new file mode 100644 index 0000000000000..1dae71a02f567 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts @@ -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 type { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { ASSET_CRITICALITY_URL, APP_ID } from '../../../../../common/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { AssetCriticalityRecordIdParts } from '../../../../../common/api/entity_analytics/asset_criticality'; +import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; +import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; +export const assetCriticalityDeleteRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .delete({ + access: 'internal', + path: ASSET_CRITICALITY_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: buildRouteValidationWithZod(AssetCriticalityRecordIdParts), + }, + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + await assetCriticalityClient.delete({ + idField: request.query.id_field, + idValue: request.query.id_value, + }); + + return response.ok(); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts new file mode 100644 index 0000000000000..dce278756ce2b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts @@ -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 type { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { ASSET_CRITICALITY_URL, APP_ID } from '../../../../../common/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; +import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; +import { AssetCriticalityRecordIdParts } from '../../../../../common/api/entity_analytics/asset_criticality'; +export const assetCriticalityGetRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { + router.versioned + .get({ + access: 'internal', + path: ASSET_CRITICALITY_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: buildRouteValidationWithZod(AssetCriticalityRecordIdParts), + }, + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const record = await assetCriticalityClient.get({ + idField: request.query.id_field, + idValue: request.query.id_value, + }); + + if (!record) { + return response.notFound(); + } + + return response.ok({ body: record }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/index.ts index c72249c3110e3..8a5f62ccff079 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/index.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/index.ts @@ -6,3 +6,6 @@ */ export { assetCriticalityStatusRoute } from './status'; +export { assetCriticalityUpsertRoute } from './upsert'; +export { assetCriticalityGetRoute } from './get'; +export { assetCriticalityDeleteRoute } from './delete'; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts index 7605d2cb099cc..cb5625c57f1ec 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts @@ -7,6 +7,7 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { AssetCriticalityStatusResponse } from '../../../../../common/api/entity_analytics/asset_criticality'; import { ASSET_CRITICALITY_STATUS_URL, APP_ID } from '../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; @@ -32,10 +33,11 @@ export const assetCriticalityStatusRoute = ( const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); const result = await assetCriticalityClient.getStatus(); + const body: AssetCriticalityStatusResponse = { + asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled, + }; return response.ok({ - body: { - asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled, - }, + body, }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts new file mode 100644 index 0000000000000..65f71bb3bfe45 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts @@ -0,0 +1,66 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { ASSET_CRITICALITY_URL, APP_ID } from '../../../../../common/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; +import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; +import { CreateAssetCriticalityRecord } from '../../../../../common/api/entity_analytics/asset_criticality'; +export const assetCriticalityUpsertRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .post({ + access: 'internal', + path: ASSET_CRITICALITY_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(CreateAssetCriticalityRecord), + }, + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + + const assetCriticalityRecord = { + idField: request.body.id_field, + idValue: request.body.id_value, + criticalityLevel: request.body.criticality_level, + }; + + const result = await assetCriticalityClient.upsert(assetCriticalityRecord); + + return response.ok({ + body: result, + }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts b/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts index 77e47f215713a..79eef256f8e93 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts @@ -11,7 +11,7 @@ import { APP_ID, RISK_SCORE_INDEX_STATUS_API_URL } from '../../../../common/cons import type { SecuritySolutionPluginRouter } from '../../../types'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../detection_engine/routes/utils'; -import { indexStatusRequestQuery } from '../../../../common/api/risk_score'; +import { indexStatusRequestQuery } from '../../../../common/api/entity_analytics/risk_score'; export const getRiskScoreIndexStatusRoute = (router: SecuritySolutionPluginRouter) => { router.versioned diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts index 22987cf563d0b..ef4aacf251ff4 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts @@ -12,7 +12,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { RISK_SCORE_CREATE_INDEX } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { createIndex } from './lib/create_index'; -import { createEsIndexRequestBody } from '../../../../common/api/risk_score'; +import { createEsIndexRequestBody } from '../../../../common/api/entity_analytics/risk_score'; export const createEsIndexRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.versioned diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts index 326963992a709..407e9e0a8f3e0 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts @@ -10,7 +10,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { RISK_SCORE_DELETE_INDICES } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { deleteEsIndices } from './lib/delete_indices'; -import { deleteIndicesRequestBody } from '../../../../common/api/risk_score'; +import { deleteIndicesRequestBody } from '../../../../common/api/entity_analytics/risk_score'; export const deleteEsIndicesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/indices/lib/create_index.ts b/x-pack/plugins/security_solution/server/lib/risk_score/indices/lib/create_index.ts index 48d3c333dff09..4104c97812e0a 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/indices/lib/create_index.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/indices/lib/create_index.ts @@ -6,7 +6,7 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { CreateEsIndexRequestBody } from '../../../../../common/api/risk_score'; +import type { CreateEsIndexRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; export const createIndex = async ({ esClient, diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts index f8afbbfae9365..627a9afd4e871 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts @@ -16,7 +16,7 @@ import type { SetupPlugins } from '../../../../plugin'; import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { installRiskScoreModule } from '../helpers/install_risk_score_module'; -import { onboardingRiskScoreRequestBody } from '../../../../../common/api/risk_score'; +import { onboardingRiskScoreRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; export const installRiskScoresRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts index 70edd32eee584..df819487e13b7 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts @@ -14,7 +14,7 @@ import { DEV_TOOL_PREBUILT_CONTENT } from '../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { consoleMappings } from '../console_mappings'; -import { readConsoleRequestBody } from '../../../../../common/api/risk_score'; +import { readConsoleRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { getView } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/schema.test.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/schema.test.ts index bf02aae37e5ca..d5856c31a5121 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/schema.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/schema.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { readConsoleRequestBody } from '../../../../common/api/risk_score'; +import { readConsoleRequestBody } from '../../../../common/api/entity_analytics/risk_score'; describe('ReadConsoleRequestSchema', () => { it('should throw error', () => { diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts index ce65983161da1..568489155707a 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts @@ -16,7 +16,7 @@ import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../timeline/utils/common'; import { bulkCreateSavedObjects } from '../helpers/bulk_create_saved_objects'; -import { createPrebuiltSavedObjectsRequestBody } from '../../../../../common/api/risk_score'; +import { createPrebuiltSavedObjectsRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; export const createPrebuiltSavedObjectsRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts index 9f2138b3c608f..c78d1f292afe6 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts @@ -16,7 +16,7 @@ import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../timeline/utils/common'; import { bulkDeleteSavedObjects } from '../helpers/bulk_delete_saved_objects'; -import { deletePrebuiltSavedObjectsRequestBody } from '../../../../../common/api/risk_score'; +import { deletePrebuiltSavedObjectsRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; export const deletePrebuiltSavedObjectsRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts index e5909892071c5..573d1d30bcd28 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts @@ -8,7 +8,7 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { createStoredScriptRequestBody } from '../../../../common/api/risk_score'; +import { createStoredScriptRequestBody } from '../../../../common/api/entity_analytics/risk_score'; import { RISK_SCORE_CREATE_STORED_SCRIPT } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { createStoredScript } from './lib/create_script'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts index 7f579b28802ec..0d7ef94be2635 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts @@ -10,7 +10,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { RISK_SCORE_DELETE_STORED_SCRIPT } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { deleteStoredScript } from './lib/delete_script'; -import { deleteStoredScriptRequestBody } from '../../../../common/api/risk_score'; +import { deleteStoredScriptRequestBody } from '../../../../common/api/entity_analytics/risk_score'; export const deleteStoredScriptRoute = (router: SecuritySolutionPluginRouter) => { router.versioned diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/create_script.ts b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/create_script.ts index fc56a3e049269..d6c2e5211625b 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/create_script.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/create_script.ts @@ -6,7 +6,7 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { CreateStoredScriptRequestBody } from '../../../../../common/api/risk_score'; +import type { CreateStoredScriptRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; export const createStoredScript = async ({ esClient, diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/delete_script.ts b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/delete_script.ts index b6113b5f9f318..bbbf8e9582ff5 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/delete_script.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/lib/delete_script.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; -import type { DeleteStoredScriptRequestBody } from '../../../../../common/api/risk_score'; +import type { DeleteStoredScriptRequestBody } from '../../../../../common/api/entity_analytics/risk_score'; export const deleteStoredScript = async ({ client, diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index de1b9d8125f7f..afe48a8d88567 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -56,6 +56,8 @@ import { registerManageExceptionsRoutes } from '../lib/exceptions/api/register_r import { registerDashboardsRoutes } from '../lib/dashboards/routes'; import { registerTagsRoutes } from '../lib/tags/routes'; import { setAlertTagsRoute } from '../lib/detection_engine/routes/signals/set_alert_tags_route'; +import { setAlertAssigneesRoute } from '../lib/detection_engine/routes/signals/set_alert_assignees_route'; +import { suggestUserProfilesRoute } from '../lib/detection_engine/routes/users/suggest_user_profiles_route'; import { riskEngineDisableRoute, riskEngineInitRoute, @@ -66,7 +68,12 @@ import { import { registerTimelineRoutes } from '../lib/timeline/routes'; import { riskScoreCalculationRoute } from '../lib/entity_analytics/risk_score/routes/calculation'; import { riskScorePreviewRoute } from '../lib/entity_analytics/risk_score/routes/preview'; -import { assetCriticalityStatusRoute } from '../lib/entity_analytics/asset_criticality/routes'; +import { + assetCriticalityStatusRoute, + assetCriticalityUpsertRoute, + assetCriticalityGetRoute, + assetCriticalityDeleteRoute, +} from '../lib/entity_analytics/asset_criticality/routes'; export const initRoutes = ( router: SecuritySolutionPluginRouter, @@ -112,11 +119,13 @@ export const initRoutes = ( // Example usage can be found in security_solution/server/lib/detection_engine/scripts/signals setSignalsStatusRoute(router, logger, security, telemetrySender); setAlertTagsRoute(router); + setAlertAssigneesRoute(router); querySignalsRoute(router, ruleDataClient); getSignalsMigrationStatusRoute(router); createSignalsMigrationRoute(router, security); finalizeSignalsMigrationRoute(router, ruleDataService, security); deleteSignalsMigrationRoute(router, security); + suggestUserProfilesRoute(router, getStartServices); // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces @@ -161,5 +170,8 @@ export const initRoutes = ( } if (config.experimentalFeatures.entityAnalyticsAssetCriticalityEnabled) { assetCriticalityStatusRoute(router, logger); + assetCriticalityUpsertRoute(router, logger); + assetCriticalityGetRoute(router, logger); + assetCriticalityDeleteRoute(router, logger); } }; diff --git a/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx index 05af48c280395..41cd10e5e3604 100644 --- a/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx @@ -16,7 +16,10 @@ import type { } from '@kbn/security-solution-upselling/service'; import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public'; import React, { lazy } from 'react'; -import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages'; +import { + UPGRADE_ALERT_ASSIGNMENTS, + UPGRADE_INVESTIGATION_GUIDE, +} from '@kbn/security-solution-upselling/messages'; import type { Services } from '../common/services'; import { withServicesProvider } from '../common/services'; const EntityAnalyticsUpsellingLazy = lazy( @@ -107,4 +110,9 @@ export const upsellingMessages: UpsellingMessages = [ minimumLicenseRequired: 'platinum', message: UPGRADE_INVESTIGATION_GUIDE('Platinum'), }, + { + id: 'alert_assignments', + minimumLicenseRequired: 'platinum', + message: UPGRADE_ALERT_ASSIGNMENTS('Platinum'), + }, ]; diff --git a/x-pack/plugins/stack_connectors/common/bedrock/constants.ts b/x-pack/plugins/stack_connectors/common/bedrock/constants.ts index ff165f6678db9..cf0f758a4b066 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/constants.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/constants.ts @@ -18,6 +18,7 @@ export enum SUB_ACTION { RUN = 'run', INVOKE_AI = 'invokeAI', INVOKE_STREAM = 'invokeStream', + DASHBOARD = 'getDashboard', TEST = 'test', } diff --git a/x-pack/plugins/stack_connectors/common/bedrock/schema.ts b/x-pack/plugins/stack_connectors/common/bedrock/schema.ts index 6fbc0252eb61b..057780a803560 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/schema.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/schema.ts @@ -52,3 +52,12 @@ export const RunActionResponseSchema = schema.object( ); export const StreamingResponseSchema = schema.any(); + +// Run action schema +export const DashboardActionParamsSchema = schema.object({ + dashboardId: schema.string(), +}); + +export const DashboardActionResponseSchema = schema.object({ + available: schema.boolean(), +}); diff --git a/x-pack/plugins/stack_connectors/common/bedrock/types.ts b/x-pack/plugins/stack_connectors/common/bedrock/types.ts index 3d9fada237987..bd27c5ed04020 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/types.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/types.ts @@ -8,6 +8,8 @@ import { TypeOf } from '@kbn/config-schema'; import { ConfigSchema, + DashboardActionParamsSchema, + DashboardActionResponseSchema, SecretsSchema, RunActionParamsSchema, RunActionResponseSchema, @@ -25,3 +27,5 @@ export type InvokeAIActionResponse = TypeOf export type RunActionResponse = TypeOf; export type StreamActionParams = TypeOf; export type StreamingResponse = TypeOf; +export type DashboardActionParams = TypeOf; +export type DashboardActionResponse = TypeOf; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx index 361caed6882c2..673d6dee8306f 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx @@ -57,5 +57,6 @@ export function getConnectorType(): BedrockConnector { }, actionConnectorFields: lazy(() => import('./connector')), actionParamsFields: lazy(() => import('./params')), + actionReadOnlyExtraComponent: lazy(() => import('./dashboard_link')), }; } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.test.tsx index 063d0e39d7ef9..9af22b79ff554 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.test.tsx @@ -8,13 +8,17 @@ import React from 'react'; import BedrockConnectorFields from './connector'; import { ConnectorFormTestProvider } from '../lib/test_utils'; -import { act, render, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import { DEFAULT_BEDROCK_MODEL } from '../../../common/bedrock/constants'; +import { useGetDashboard } from '../lib/gen_ai/use_get_dashboard'; jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); +jest.mock('../lib/gen_ai/use_get_dashboard'); + const useKibanaMock = useKibana as jest.Mocked; +const mockDashboard = useGetDashboard as jest.Mock; const bedrockConnector = { actionTypeId: '.bedrock', name: 'bedrock', @@ -36,6 +40,9 @@ describe('BedrockConnectorFields renders', () => { beforeEach(() => { jest.clearAllMocks(); useKibanaMock().services.application.navigateToUrl = navigateToUrl; + mockDashboard.mockImplementation(({ connectorId }) => ({ + dashboardUrl: `https://dashboardurl.com/${connectorId}`, + })); }); test('Bedrock connector fields are rendered', async () => { const { getAllByTestId } = render( @@ -58,6 +65,49 @@ describe('BedrockConnectorFields renders', () => { expect(getAllByTestId('bedrock-api-model-doc')[0]).toBeInTheDocument(); }); + describe('Dashboard link', () => { + it('Does not render if isEdit is false and dashboardUrl is defined', async () => { + const { queryByTestId } = render( + + {}} + /> + + ); + expect(queryByTestId('link-gen-ai-token-dashboard')).not.toBeInTheDocument(); + }); + it('Does not render if isEdit is true and dashboardUrl is null', async () => { + mockDashboard.mockImplementation((id: string) => ({ + dashboardUrl: null, + })); + const { queryByTestId } = render( + + {}} /> + + ); + expect(queryByTestId('link-gen-ai-token-dashboard')).not.toBeInTheDocument(); + }); + it('Renders if isEdit is true and dashboardUrl is defined', async () => { + const { getByTestId } = render( + + {}} /> + + ); + expect(getByTestId('link-gen-ai-token-dashboard')).toBeInTheDocument(); + }); + it('On click triggers redirect with correct saved object id', async () => { + const { getByTestId } = render( + + {}} /> + + ); + fireEvent.click(getByTestId('link-gen-ai-token-dashboard')); + expect(navigateToUrl).toHaveBeenCalledWith(`https://dashboardurl.com/123`); + }); + }); + describe('Validation', () => { const onSubmit = jest.fn(); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.tsx index c99574aaf5c43..82927bea6ac7b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/connector.tsx @@ -10,16 +10,23 @@ import { ActionConnectorFieldsProps, SimpleConnectorForm, } from '@kbn/triggers-actions-ui-plugin/public'; +import { useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import DashboardLink from './dashboard_link'; +import { BEDROCK } from './translations'; import { bedrockConfig, bedrockSecrets } from './constants'; const BedrockConnectorFields: React.FC = ({ readOnly, isEdit }) => { + const [{ id, name }] = useFormData(); return ( - + <> + + {isEdit && } + ); }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/dashboard_link.tsx b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/dashboard_link.tsx new file mode 100644 index 0000000000000..e9541c3f6759c --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/dashboard_link.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, { useCallback } from 'react'; +import { EuiLink } from '@elastic/eui'; +import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; +import * as i18n from './translations'; +import { useGetDashboard } from '../lib/gen_ai/use_get_dashboard'; + +interface Props { + connectorId: string; + connectorName: string; + selectedProvider?: string; +} +// tested from ./connector.test.tsx +export const DashboardLink: React.FC = ({ + connectorId, + connectorName, + selectedProvider = 'Bedrock', +}) => { + const { dashboardUrl } = useGetDashboard({ connectorId, selectedProvider }); + const { + services: { + application: { navigateToUrl }, + }, + } = useKibana(); + const onClick = useCallback( + (e) => { + e.preventDefault(); + if (dashboardUrl) { + navigateToUrl(dashboardUrl); + } + }, + [dashboardUrl, navigateToUrl] + ); + return dashboardUrl != null ? ( + // href gives us right click -> open in new tab + // onclick prevents page reload + // eslint-disable-next-line @elastic/eui/href-or-on-click + + {i18n.USAGE_DASHBOARD_LINK(selectedProvider, connectorName)} + + ) : null; +}; + +// eslint-disable-next-line import/no-default-export +export { DashboardLink as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/translations.ts index 6906f691ffcae..90c593fa602d7 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/translations.ts @@ -97,3 +97,9 @@ export const BODY_DESCRIPTION = i18n.translate( export const MODEL = i18n.translate('xpack.stackConnectors.components.bedrock.model', { defaultMessage: 'Model', }); + +export const USAGE_DASHBOARD_LINK = (apiProvider: string, connectorName: string) => + i18n.translate('xpack.stackConnectors.components.genAi.dashboardLink', { + values: { apiProvider, connectorName }, + defaultMessage: 'View {apiProvider} Usage Dashboard for "{ connectorName }" Connector', + }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/api.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/api.test.ts similarity index 94% rename from x-pack/plugins/stack_connectors/public/connector_types/openai/api.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/api.test.ts index 5ab342a22828b..c48ca17eaced3 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/api.test.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/api.test.ts @@ -7,7 +7,7 @@ import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { getDashboard } from './api'; -import { SUB_ACTION } from '../../../common/openai/constants'; +import { SUB_ACTION } from '../../../../common/openai/constants'; const response = { available: true, }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/api.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/api.ts similarity index 91% rename from x-pack/plugins/stack_connectors/public/connector_types/openai/api.ts rename to x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/api.ts index 97b0608bb725d..07780e8d368f2 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/api.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/api.ts @@ -7,8 +7,8 @@ import { HttpSetup } from '@kbn/core-http-browser'; import { ActionTypeExecutorResult, BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; -import { SUB_ACTION } from '../../../common/openai/constants'; -import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../lib/rewrite_response_body'; +import { SUB_ACTION } from '../../../../common/openai/constants'; +import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; export async function getDashboard({ http, diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/translations.ts new file mode 100644 index 0000000000000..78c79863a23a9 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const GET_DASHBOARD_API_ERROR = (apiProvider: string) => + i18n.translate('xpack.stackConnectors.components.genAi.error.dashboardApiError', { + values: { apiProvider }, + defaultMessage: 'Error finding {apiProvider} Token Usage Dashboard.', + }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/use_get_dashboard.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts similarity index 70% rename from x-pack/plugins/stack_connectors/public/connector_types/openai/use_get_dashboard.test.ts rename to x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts index 8e78c522712bd..8ca9b97292fa3 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/use_get_dashboard.test.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts @@ -39,7 +39,9 @@ const mockServices = { const mockDashboard = getDashboard as jest.Mock; const mockKibana = useKibana as jest.Mock; -describe('useGetDashboard_function', () => { +const defaultArgs = { connectorId, selectedProvider: 'OpenAI' }; + +describe('useGetDashboard', () => { beforeEach(() => { jest.clearAllMocks(); mockDashboard.mockResolvedValue({ data: { available: true } }); @@ -48,36 +50,45 @@ describe('useGetDashboard_function', () => { }); }); - it('fetches the dashboard and sets the dashboard URL', async () => { - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard({ connectorId })); - await waitForNextUpdate(); - expect(mockDashboard).toHaveBeenCalledWith( - expect.objectContaining({ - connectorId, - dashboardId: 'generative-ai-token-usage-space', - }) - ); - expect(mockGetRedirectUrl).toHaveBeenCalledWith({ - query: { - language: 'kuery', - query: `kibana.saved_objects: { id : ${connectorId} }`, - }, - dashboardId: 'generative-ai-token-usage-space', - }); - expect(result.current.isLoading).toBe(false); - expect(result.current.dashboardUrl).toBe( - 'http://localhost:5601/app/dashboards#/view/generative-ai-token-usage-space' - ); - }); + it.each([ + ['Azure OpenAI', 'openai'], + ['OpenAI', 'openai'], + ['Bedrock', 'bedrock'], + ])( + 'fetches the %p dashboard and sets the dashboard URL with %p', + async (selectedProvider, urlKey) => { + const { result, waitForNextUpdate } = renderHook(() => + useGetDashboard({ ...defaultArgs, selectedProvider }) + ); + await waitForNextUpdate(); + expect(mockDashboard).toHaveBeenCalledWith( + expect.objectContaining({ + connectorId, + dashboardId: `generative-ai-token-usage-${urlKey}-space`, + }) + ); + expect(mockGetRedirectUrl).toHaveBeenCalledWith({ + query: { + language: 'kuery', + query: `kibana.saved_objects: { id : ${connectorId} }`, + }, + dashboardId: `generative-ai-token-usage-${urlKey}-space`, + }); + expect(result.current.isLoading).toBe(false); + expect(result.current.dashboardUrl).toBe( + `http://localhost:5601/app/dashboards#/view/generative-ai-token-usage-${urlKey}-space` + ); + } + ); it('handles the case where the dashboard is not available.', async () => { mockDashboard.mockResolvedValue({ data: { available: false } }); - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard({ connectorId })); + const { result, waitForNextUpdate } = renderHook(() => useGetDashboard(defaultArgs)); await waitForNextUpdate(); expect(mockDashboard).toHaveBeenCalledWith( expect.objectContaining({ connectorId, - dashboardId: 'generative-ai-token-usage-space', + dashboardId: 'generative-ai-token-usage-openai-space', }) ); expect(mockGetRedirectUrl).not.toHaveBeenCalled(); @@ -91,7 +102,7 @@ describe('useGetDashboard_function', () => { services: { ...mockServices, spaces: null }, }); - const { result } = renderHook(() => useGetDashboard({ connectorId })); + const { result } = renderHook(() => useGetDashboard(defaultArgs)); expect(mockDashboard).not.toHaveBeenCalled(); expect(mockGetRedirectUrl).not.toHaveBeenCalled(); expect(result.current.isLoading).toBe(false); @@ -99,7 +110,9 @@ describe('useGetDashboard_function', () => { }); it('handles the case where connectorId is empty string', async () => { - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard({ connectorId: '' })); + const { result, waitForNextUpdate } = renderHook(() => + useGetDashboard({ ...defaultArgs, connectorId: '' }) + ); await waitForNextUpdate(); expect(mockDashboard).not.toHaveBeenCalled(); expect(mockGetRedirectUrl).not.toHaveBeenCalled(); @@ -111,7 +124,7 @@ describe('useGetDashboard_function', () => { mockKibana.mockReturnValue({ services: { ...mockServices, dashboard: {} }, }); - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard({ connectorId })); + const { result, waitForNextUpdate } = renderHook(() => useGetDashboard(defaultArgs)); await waitForNextUpdate(); expect(result.current.isLoading).toBe(false); expect(result.current.dashboardUrl).toBe(null); @@ -119,7 +132,7 @@ describe('useGetDashboard_function', () => { it('correctly handles errors and displays the appropriate toast messages.', async () => { mockDashboard.mockRejectedValue(new Error('Error fetching dashboard')); - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard({ connectorId })); + const { result, waitForNextUpdate } = renderHook(() => useGetDashboard(defaultArgs)); await waitForNextUpdate(); expect(result.current.isLoading).toBe(false); expect(mockToasts.addDanger).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/use_get_dashboard.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.ts similarity index 81% rename from x-pack/plugins/stack_connectors/public/connector_types/openai/use_get_dashboard.ts rename to x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.ts index 557cf2e331ca6..dd1f6596d9201 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/use_get_dashboard.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.ts @@ -7,20 +7,20 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; -import { getDashboardId } from './constants'; + import { getDashboard } from './api'; import * as i18n from './translations'; interface Props { connectorId: string; + selectedProvider: string; } export interface UseGetDashboard { dashboardUrl: string | null; isLoading: boolean; } - -export const useGetDashboard = ({ connectorId }: Props): UseGetDashboard => { +export const useGetDashboard = ({ connectorId, selectedProvider }: Props): UseGetDashboard => { const { dashboard, http, @@ -84,7 +84,7 @@ export const useGetDashboard = ({ connectorId }: Props): UseGetDashboard => { if (res.status && res.status === 'error') { toasts.addDanger({ - title: i18n.GET_DASHBOARD_API_ERROR, + title: i18n.GET_DASHBOARD_API_ERROR(selectedProvider), text: `${res.serviceMessage ?? res.message}`, }); } @@ -94,7 +94,7 @@ export const useGetDashboard = ({ connectorId }: Props): UseGetDashboard => { setDashboardCheckComplete(true); setIsLoading(false); toasts.addDanger({ - title: i18n.GET_DASHBOARD_API_ERROR, + title: i18n.GET_DASHBOARD_API_ERROR(selectedProvider), text: error.message, }); } @@ -103,7 +103,7 @@ export const useGetDashboard = ({ connectorId }: Props): UseGetDashboard => { if (spaceId != null && connectorId.length > 0 && !dashboardCheckComplete) { abortCtrl.current.abort(); - fetchData(getDashboardId(spaceId)); + fetchData(getDashboardId(selectedProvider, spaceId)); } return () => { @@ -111,10 +111,24 @@ export const useGetDashboard = ({ connectorId }: Props): UseGetDashboard => { setIsLoading(false); abortCtrl.current.abort(); }; - }, [connectorId, dashboardCheckComplete, dashboardUrl, http, setUrl, spaceId, toasts]); + }, [ + connectorId, + dashboardCheckComplete, + dashboardUrl, + http, + selectedProvider, + setUrl, + spaceId, + toasts, + ]); return { isLoading, dashboardUrl, }; }; + +const getDashboardId = (selectedProvider: string, spaceId: string): string => + `generative-ai-token-usage-${ + selectedProvider.toLowerCase().includes('openai') ? 'openai' : 'bedrock' + }-${spaceId}`; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/helpers.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/helpers.ts index f3e71b0e9fc7f..65019f06bf3f6 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/helpers.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/helpers.ts @@ -16,6 +16,8 @@ import { AppInfo, Choice, RESTApiError } from './types'; export const DEFAULT_CORRELATION_ID = '{{rule.id}}:{{alert.id}}'; +export const ACTION_GROUP_RECOVERED = 'recovered'; + export const choicesToEuiOptions = (choices: Choice[]): EuiSelectOption[] => choices.map((choice) => ({ value: choice.value, text: choice.label })); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts index f55a82b4ce00c..a6f7cd65dfe40 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts @@ -56,6 +56,13 @@ export const TITLE_REQUIRED = i18n.translate( } ); +export const CORRELATION_ID_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.serviceNow.requiredCorrelationIdTextField', + { + defaultMessage: 'Correlation id is required.', + } +); + export const INCIDENT = i18n.translate('xpack.stackConnectors.components.serviceNow.title', { defaultMessage: 'Incident', }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/openai/connector.test.tsx index e88c3fa116153..c434455076d17 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/connector.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/openai/connector.test.tsx @@ -12,10 +12,10 @@ import { act, fireEvent, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { DEFAULT_OPENAI_MODEL, OpenAiProviderType } from '../../../common/openai/constants'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; -import { useGetDashboard } from './use_get_dashboard'; +import { useGetDashboard } from '../lib/gen_ai/use_get_dashboard'; jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); -jest.mock('./use_get_dashboard'); +jest.mock('../lib/gen_ai/use_get_dashboard'); const useKibanaMock = useKibana as jest.Mocked; const mockDashboard = useGetDashboard as jest.Mock; @@ -101,7 +101,7 @@ describe('ConnectorFields renders', () => { })); const { queryByTestId } = render( - {}} /> + {}} /> ); expect(queryByTestId('link-gen-ai-token-dashboard')).not.toBeInTheDocument(); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/constants.tsx b/x-pack/plugins/stack_connectors/public/connector_types/openai/constants.tsx index 4df722ecfae04..7231a41209f82 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/constants.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/openai/constants.tsx @@ -154,5 +154,3 @@ export const providerOptions = [ label: i18n.AZURE_AI, }, ]; - -export const getDashboardId = (spaceId: string): string => `generative-ai-token-usage-${spaceId}`; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/dashboard_link.tsx b/x-pack/plugins/stack_connectors/public/connector_types/openai/dashboard_link.tsx index b7d6ef972372d..85c1a9a1955b6 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/dashboard_link.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/openai/dashboard_link.tsx @@ -9,7 +9,7 @@ import React, { useCallback } from 'react'; import { EuiLink } from '@elastic/eui'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; -import { useGetDashboard } from './use_get_dashboard'; +import { useGetDashboard } from '../lib/gen_ai/use_get_dashboard'; interface Props { connectorId: string; @@ -20,9 +20,9 @@ interface Props { export const DashboardLink: React.FC = ({ connectorId, connectorName, - selectedProvider = '', + selectedProvider = 'OpenAI', }) => { - const { dashboardUrl } = useGetDashboard({ connectorId }); + const { dashboardUrl } = useGetDashboard({ connectorId, selectedProvider }); const { services: { application: { navigateToUrl }, @@ -38,7 +38,10 @@ export const DashboardLink: React.FC = ({ [dashboardUrl, navigateToUrl] ); return dashboardUrl != null ? ( - + // href gives us right click -> open in new tab + // onclick prevents page reload + // eslint-disable-next-line @elastic/eui/href-or-on-click + {i18n.USAGE_DASHBOARD_LINK(selectedProvider, connectorName)} ) : null; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/openai/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/openai/translations.ts index f6cfa4a91cf61..4c72866c6ece4 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/openai/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/openai/translations.ts @@ -100,10 +100,3 @@ export const USAGE_DASHBOARD_LINK = (apiProvider: string, connectorName: string) values: { apiProvider, connectorName }, defaultMessage: 'View {apiProvider} Usage Dashboard for "{ connectorName }" Connector', }); - -export const GET_DASHBOARD_API_ERROR = i18n.translate( - 'xpack.stackConnectors.components.genAi.error.dashboardApiError', - { - defaultMessage: 'Error finding OpenAI Token Usage Dashboard.', - } -); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx index ea7497bde2837..107ccab01e60c 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx @@ -35,7 +35,25 @@ describe('servicenow action params validation', () => { }; expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ - errors: { ['subActionParams.incident.short_description']: [] }, + errors: { + ['subActionParams.incident.correlation_id']: [], + ['subActionParams.incident.short_description']: [], + }, + }); + }); + + test(`${SERVICENOW_ITSM_CONNECTOR_TYPE_ID}: action params validation succeeds for closeIncident subAction`, async () => { + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITSM_CONNECTOR_TYPE_ID); + const actionParams = { + subAction: 'closeIncident', + subActionParams: { incident: { correlation_id: '{{test}}{{rule_id}}' } }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + ['subActionParams.incident.correlation_id']: [], + ['subActionParams.incident.short_description']: [], + }, }); }); @@ -47,8 +65,24 @@ describe('servicenow action params validation', () => { expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { + ['subActionParams.incident.correlation_id']: [], ['subActionParams.incident.short_description']: ['Short description is required.'], }, }); }); + + test(`${SERVICENOW_ITSM_CONNECTOR_TYPE_ID}: params validation fails when correlation_id is not valid and subAction is closeIncident`, async () => { + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITSM_CONNECTOR_TYPE_ID); + const actionParams = { + subAction: 'closeIncident', + subActionParams: { incident: { correlation_id: '' } }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + ['subActionParams.incident.correlation_id']: ['Correlation id is required.'], + ['subActionParams.incident.short_description']: [], + }, + }); + }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx index 23e0aba04e016..dfa6eb5c43987 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx @@ -13,7 +13,11 @@ import type { } from '@kbn/triggers-actions-ui-plugin/public'; import { ServiceNowConfig, ServiceNowSecrets } from '../lib/servicenow/types'; import { ServiceNowITSMActionParams } from './types'; -import { getConnectorDescriptiveTitle, getSelectedConnectorIcon } from '../lib/servicenow/helpers'; +import { + DEFAULT_CORRELATION_ID, + getConnectorDescriptiveTitle, + getSelectedConnectorIcon, +} from '../lib/servicenow/helpers'; export const SERVICENOW_ITSM_DESC = i18n.translate( 'xpack.stackConnectors.components.serviceNowITSM.selectMessageText', @@ -46,6 +50,7 @@ export function getServiceNowITSMConnectorType(): ConnectorTypeModel< const translations = await import('../lib/servicenow/translations'); const errors = { 'subActionParams.incident.short_description': new Array(), + 'subActionParams.incident.correlation_id': new Array(), }; const validationResult = { errors, @@ -53,10 +58,20 @@ export function getServiceNowITSMConnectorType(): ConnectorTypeModel< if ( actionParams.subActionParams && actionParams.subActionParams.incident && + actionParams.subAction !== 'closeIncident' && !actionParams.subActionParams.incident.short_description?.length ) { errors['subActionParams.incident.short_description'].push(translations.TITLE_REQUIRED); } + + if ( + actionParams.subAction === 'closeIncident' && + !actionParams?.subActionParams?.incident?.correlation_id?.length + ) { + errors['subActionParams.incident.correlation_id'].push( + translations.CORRELATION_ID_REQUIRED + ); + } return validationResult; }, actionParamsFields: lazy(() => import('./servicenow_itsm_params')), @@ -64,5 +79,18 @@ export function getServiceNowITSMConnectorType(): ConnectorTypeModel< getText: getConnectorDescriptiveTitle, getComponent: getSelectedConnectorIcon, }, + defaultActionParams: { + subAction: 'pushToService', + subActionParams: { + incident: { correlation_id: DEFAULT_CORRELATION_ID }, + comments: [], + }, + }, + defaultRecoveredActionParams: { + subAction: 'closeIncident', + subActionParams: { + incident: { correlation_id: DEFAULT_CORRELATION_ID }, + }, + }, }; } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx index 5925724b7b8a9..a9d303ee4f20d 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx @@ -14,6 +14,7 @@ import { useGetChoices } from '../lib/servicenow/use_get_choices'; import ServiceNowITSMParamsFields from './servicenow_itsm_params'; import { Choice } from '../lib/servicenow/types'; import { merge } from 'lodash'; +import { ACTION_GROUP_RECOVERED } from '../lib/servicenow/helpers'; jest.mock('../lib/servicenow/use_get_choices'); jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); @@ -151,14 +152,11 @@ describe('ServiceNowITSMParamsFields renders', () => { expect(title.prop('isInvalid')).toBeTruthy(); }); - test('When subActionParams is undefined, set to default', () => { - const { subActionParams, ...newParams } = actionParams; - - const newProps = { - ...defaultProps, - actionParams: newParams, - }; - mountWithIntl(); + test('Resets fields when connector changes', () => { + const wrapper = mountWithIntl(); + expect(editAction.mock.calls.length).toEqual(0); + wrapper.setProps({ actionConnector: { ...connector, id: '1234' } }); + expect(editAction.mock.calls.length).toEqual(1); expect(editAction.mock.calls[0][1]).toEqual({ incident: { correlation_id: '{{rule.id}}:{{alert.id}}', @@ -167,27 +165,17 @@ describe('ServiceNowITSMParamsFields renders', () => { }); }); - test('When subAction is undefined, set to default', () => { - const { subAction, ...newParams } = actionParams; - + test('Resets fields when connector changes and action group is recovered', () => { const newProps = { ...defaultProps, - actionParams: newParams, + selectedActionGroupId: ACTION_GROUP_RECOVERED, }; - mountWithIntl(); - expect(editAction.mock.calls[0][1]).toEqual('pushToService'); - }); - - test('Resets fields when connector changes', () => { - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); expect(editAction.mock.calls.length).toEqual(0); wrapper.setProps({ actionConnector: { ...connector, id: '1234' } }); expect(editAction.mock.calls.length).toEqual(1); expect(editAction.mock.calls[0][1]).toEqual({ - incident: { - correlation_id: '{{rule.id}}:{{alert.id}}', - }, - comments: [], + incident: { correlation_id: '{{rule.id}}:{{alert.id}}' }, }); }); @@ -299,5 +287,57 @@ describe('ServiceNowITSMParamsFields renders', () => { expect(comments.simulate('change', changeEvent)); expect(editAction.mock.calls[0][1].comments.length).toEqual(1); }); + + test('shows only correlation_id field when actionGroup is recovered', () => { + const newProps = { + ...defaultProps, + selectedActionGroupId: 'recovered', + }; + const wrapper = mountWithIntl(); + expect(wrapper.find('input[data-test-subj="correlation_idInput"]').exists()).toBeTruthy(); + expect(wrapper.find('input[data-test-subj="short_descriptionInput"]').exists()).toBeFalsy(); + }); + + test('A short description change triggers editAction', () => { + const wrapper = mountWithIntl( + + ); + + const shortDescriptionField = wrapper.find('input[data-test-subj="short_descriptionInput"]'); + shortDescriptionField.simulate('change', { + target: { value: 'new updated short description' }, + }); + + expect(editAction.mock.calls[0][1]).toEqual({ + incident: { short_description: 'new updated short description' }, + comments: [], + }); + }); + + test('A correlation_id field change triggers edit action correctly when actionGroup is recovered', () => { + const wrapper = mountWithIntl( + + ); + const correlationIdField = wrapper.find('input[data-test-subj="correlation_idInput"]'); + + correlationIdField.simulate('change', { + target: { value: 'updated correlation id' }, + }); + + expect(editAction.mock.calls[0][1]).toEqual({ + incident: { correlation_id: 'updated correlation id' }, + }); + }); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx index 9da2069bc29f5..7c0c72db2b502 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx @@ -25,7 +25,11 @@ import { import { Choice, Fields } from '../lib/servicenow/types'; import { ServiceNowITSMActionParams } from './types'; import { useGetChoices } from '../lib/servicenow/use_get_choices'; -import { choicesToEuiOptions, DEFAULT_CORRELATION_ID } from '../lib/servicenow/helpers'; +import { + ACTION_GROUP_RECOVERED, + choicesToEuiOptions, + DEFAULT_CORRELATION_ID, +} from '../lib/servicenow/helpers'; import * as i18n from '../lib/servicenow/translations'; @@ -39,11 +43,50 @@ const defaultFields: Fields = { priority: [], }; +const CorrelationIdField: React.FunctionComponent< + Pick, 'index' | 'messageVariables'> & { + correlationId: string | null; + editSubActionProperty: (key: string, value: any) => void; + } +> = ({ index, messageVariables, correlationId, editSubActionProperty }) => { + const { docLinks } = useKibana().services; + return ( + + + + } + > + + + ); +}; + const ServiceNowParamsFields: React.FunctionComponent< ActionParamsProps -> = ({ actionConnector, actionParams, editAction, index, errors, messageVariables }) => { +> = (props) => { + const { + actionConnector, + actionParams, + editAction, + index, + errors, + messageVariables, + selectedActionGroupId, + } = props; const { - docLinks, http, notifications: { toasts }, } = useKibana().services; @@ -56,9 +99,9 @@ const ServiceNowParamsFields: React.FunctionComponent< actionParams.subActionParams ?? ({ incident: {}, - comments: [], + comments: selectedActionGroupId !== ACTION_GROUP_RECOVERED ? [] : undefined, } as unknown as ServiceNowITSMActionParams['subActionParams']), - [actionParams.subActionParams] + [actionParams.subActionParams, selectedActionGroupId] ); const [choices, setChoices] = useState(defaultFields); @@ -122,23 +165,16 @@ const ServiceNowParamsFields: React.FunctionComponent< useEffect(() => { if (actionConnector != null && actionConnectorRef.current !== actionConnector.id) { actionConnectorRef.current = actionConnector.id; - editAction( - 'subActionParams', - { - incident: { correlation_id: DEFAULT_CORRELATION_ID }, - comments: [], - }, - index - ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionConnector]); + if (selectedActionGroupId === ACTION_GROUP_RECOVERED) { + editAction( + 'subActionParams', + { incident: { correlation_id: DEFAULT_CORRELATION_ID } }, + index + ); + + return; + } - useEffect(() => { - if (!actionParams.subAction) { - editAction('subAction', 'pushToService', index); - } - if (!actionParams.subActionParams) { editAction( 'subActionParams', { @@ -149,7 +185,7 @@ const ServiceNowParamsFields: React.FunctionComponent< ); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionParams]); + }, [actionConnector]); return ( <> @@ -157,173 +193,170 @@ const ServiceNowParamsFields: React.FunctionComponent<

{i18n.INCIDENT}

- - editSubActionProperty('urgency', e.target.value)} - /> - - - - - - editSubActionProperty('severity', e.target.value)} - /> - - - - - editSubActionProperty('impact', e.target.value)} - /> - - - - - - - + {selectedActionGroupId !== ACTION_GROUP_RECOVERED ? ( + <> + { - editAction( - 'subActionParams', - { - incident: { ...incident, category: e.target.value, subcategory: null }, - comments, - }, - index - ); - }} + options={urgencyOptions} + value={incident.urgency ?? ''} + onChange={(e) => editSubActionProperty('urgency', e.target.value)} /> - - - {subcategoryOptions?.length > 0 ? ( - - editSubActionProperty('subcategory', e.target.value)} - /> - - ) : null} - - - - {!isDeprecatedActionConnector && ( - <> + - - - - } - > - + editSubActionProperty('severity', e.target.value)} + /> + + + + + editSubActionProperty('impact', e.target.value)} + /> + + + + + + + + { + editAction( + 'subActionParams', + { + incident: { ...incident, category: e.target.value, subcategory: null }, + comments, + }, + index + ); + }} /> - + {subcategoryOptions?.length > 0 ? ( + + editSubActionProperty('subcategory', e.target.value)} + /> + + ) : null} + + + + {!isDeprecatedActionConnector && ( + <> + + + + + + + + + + + + + )} + + + 0 && + incident.short_description !== undefined + } + label={i18n.SHORT_DESCRIPTION_LABEL} + > + + 0 ? comments[0].comment : undefined} + label={i18n.COMMENTS_LABEL} + /> + ) : ( + )} - - - 0 && - incident.short_description !== undefined - } - label={i18n.SHORT_DESCRIPTION_LABEL} - > - - - - - - - 0 ? comments[0].comment : undefined} - label={i18n.COMMENTS_LABEL} - /> ); }; 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 0eeb309dd2257..a3fd59e0ccc0e 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 @@ -18,7 +18,9 @@ import { DEFAULT_TOKEN_LIMIT, } from '../../../common/bedrock/constants'; import { DEFAULT_BODY } from '../../../public/connector_types/bedrock/constants'; +import { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; import { AxiosError } from 'axios'; +jest.mock('../lib/gen_ai/create_gen_ai_dashboard'); // @ts-ignore const mockSigner = jest.spyOn(aws, 'sign').mockReturnValue({ signed: true }); @@ -41,18 +43,19 @@ describe('BedrockConnector', () => { }); }); + const connector = new BedrockConnector({ + configurationUtilities: actionsConfigMock.create(), + connector: { id: '1', type: BEDROCK_CONNECTOR_ID }, + config: { + apiUrl: DEFAULT_BEDROCK_URL, + defaultModel: DEFAULT_BEDROCK_MODEL, + }, + secrets: { accessKey: '123', secret: 'secret' }, + logger: loggingSystemMock.createLogger(), + services: actionsMock.createServices(), + }); + describe('Bedrock', () => { - const connector = new BedrockConnector({ - configurationUtilities: actionsConfigMock.create(), - connector: { id: '1', type: BEDROCK_CONNECTOR_ID }, - config: { - apiUrl: DEFAULT_BEDROCK_URL, - defaultModel: DEFAULT_BEDROCK_MODEL, - }, - secrets: { accessKey: '123', secret: 'secret' }, - logger: loggingSystemMock.createLogger(), - services: actionsMock.createServices(), - }); beforeEach(() => { // @ts-ignore connector.request = mockRequest; @@ -335,6 +338,74 @@ describe('BedrockConnector', () => { }); }); }); + + describe('Token dashboard', () => { + const mockGenAi = initDashboard as jest.Mock; + beforeEach(() => { + // @ts-ignore + connector.esClient.transport.request = mockRequest; + mockRequest.mockResolvedValue({ has_all_requested: true }); + mockGenAi.mockResolvedValue({ success: true }); + jest.clearAllMocks(); + }); + it('the create dashboard API call returns available: true when user has correct permissions', async () => { + const response = await connector.getDashboard({ dashboardId: '123' }); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + index: [ + { + names: ['.kibana-event-log-*'], + allow_restricted_indices: true, + privileges: ['read'], + }, + ], + }, + }); + expect(response).toEqual({ available: true }); + }); + it('the create dashboard API call returns available: false when user has correct permissions', async () => { + mockRequest.mockResolvedValue({ has_all_requested: false }); + const response = await connector.getDashboard({ dashboardId: '123' }); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + index: [ + { + names: ['.kibana-event-log-*'], + allow_restricted_indices: true, + privileges: ['read'], + }, + ], + }, + }); + expect(response).toEqual({ available: false }); + }); + + it('the create dashboard API call returns available: false when init dashboard fails', async () => { + mockGenAi.mockResolvedValue({ success: false }); + const response = await connector.getDashboard({ dashboardId: '123' }); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + index: [ + { + names: ['.kibana-event-log-*'], + allow_restricted_indices: true, + privileges: ['read'], + }, + ], + }, + }); + expect(response).toEqual({ available: false }); + }); + }); }); function createStreamMock() { 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 ade589e54dc14..a5fda6a05afa6 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 @@ -10,6 +10,7 @@ import aws from 'aws4'; import type { AxiosError } from 'axios'; import { IncomingMessage } from 'http'; import { PassThrough } from 'stream'; +import { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; import { RunActionParamsSchema, RunActionResponseSchema, @@ -26,7 +27,12 @@ import type { StreamActionParams, } from '../../../common/bedrock/types'; import { SUB_ACTION, DEFAULT_TOKEN_LIMIT } from '../../../common/bedrock/constants'; -import { StreamingResponse } from '../../../common/bedrock/types'; +import { + DashboardActionParams, + DashboardActionResponse, + StreamingResponse, +} from '../../../common/bedrock/types'; +import { DashboardActionParamsSchema } from '../../../common/bedrock/schema'; interface SignedRequest { host: string; @@ -55,6 +61,12 @@ export class BedrockConnector extends SubActionConnector { schema: RunActionParamsSchema, }); + this.registerSubAction({ + name: SUB_ACTION.DASHBOARD, + method: 'getDashboard', + schema: DashboardActionParamsSchema, + }); + this.registerSubAction({ name: SUB_ACTION.TEST, method: 'runApi', @@ -119,6 +131,42 @@ export class BedrockConnector extends SubActionConnector { ) as SignedRequest; } + /** + * retrieves a dashboard from the Kibana server and checks if the + * user has the necessary privileges to access it. + * @param dashboardId The ID of the dashboard to retrieve. + */ + public async getDashboard({ + dashboardId, + }: DashboardActionParams): Promise { + const privilege = (await this.esClient.transport.request({ + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + index: [ + { + names: ['.kibana-event-log-*'], + allow_restricted_indices: true, + privileges: ['read'], + }, + ], + }, + })) as { has_all_requested: boolean }; + + if (!privilege?.has_all_requested) { + return { available: false }; + } + + const response = await initDashboard({ + logger: this.logger, + savedObjectsClient: this.savedObjectsClient, + dashboardId, + genAIProvider: 'Bedrock', + }); + + return { available: response.success }; + } + /** * responsible for making a POST request to the external API endpoint and returning the response data * @param body The stringified request body to be sent in the POST request. @@ -186,9 +234,8 @@ export class BedrockConnector extends SubActionConnector { /** * Deprecated. Use invokeStream instead. - * TODO: remove before 8.12 FF in part 3 of streaming work for security solution + * TODO: remove once streaming work is implemented in langchain mode for security solution * tracked here: https://github.com/elastic/security-team/issues/7363 - * No token tracking implemented for this method */ public async invokeAI({ messages, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/create_dashboard.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/create_gen_ai_dashboard.test.ts similarity index 81% rename from x-pack/plugins/stack_connectors/server/connector_types/openai/create_dashboard.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/create_gen_ai_dashboard.test.ts index 490f7f7cfc23f..ff23c96014d8c 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/create_dashboard.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/create_gen_ai_dashboard.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { initDashboard } from './create_dashboard'; -import { getDashboard } from './dashboard'; +import { initDashboard } from './create_gen_ai_dashboard'; +import { getDashboard } from './gen_ai_dashboard'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { Logger } from '@kbn/logging'; @@ -16,22 +16,22 @@ jest.mock('uuid', () => ({ })); const logger = loggingSystemMock.create().get() as jest.Mocked; +const dashboardId = 'test-dashboard-id'; const savedObjectsClient = savedObjectsClientMock.create(); +const defaultArgs = { logger, savedObjectsClient, dashboardId, genAIProvider: 'OpenAI' as const }; describe('createDashboard', () => { beforeEach(() => { jest.clearAllMocks(); }); it('fetches the Gen Ai Dashboard saved object', async () => { - const dashboardId = 'test-dashboard-id'; - const result = await initDashboard({ logger, savedObjectsClient, dashboardId }); + const result = await initDashboard(defaultArgs); expect(result.success).toBe(true); expect(logger.error).not.toHaveBeenCalled(); expect(savedObjectsClient.get).toHaveBeenCalledWith('dashboard', dashboardId); }); it('creates the Gen Ai Dashboard saved object when the dashboard saved object does not exist', async () => { - const dashboardId = 'test-dashboard-id'; const soClient = { ...savedObjectsClient, get: jest.fn().mockRejectedValue({ @@ -46,12 +46,12 @@ describe('createDashboard', () => { }, }), }; - const result = await initDashboard({ logger, savedObjectsClient: soClient, dashboardId }); + const result = await initDashboard({ ...defaultArgs, savedObjectsClient: soClient }); expect(soClient.get).toHaveBeenCalledWith('dashboard', dashboardId); expect(soClient.create).toHaveBeenCalledWith( 'dashboard', - getDashboard(dashboardId).attributes, + getDashboard(defaultArgs.genAIProvider, dashboardId).attributes, { overwrite: true, id: dashboardId } ); expect(result.success).toBe(true); @@ -72,8 +72,7 @@ describe('createDashboard', () => { }, }), }; - const dashboardId = 'test-dashboard-id'; - const result = await initDashboard({ logger, savedObjectsClient: soClient, dashboardId }); + const result = await initDashboard({ ...defaultArgs, savedObjectsClient: soClient }); expect(result.success).toBe(false); expect(result.error?.message).toBe('Internal Server Error: Error happened'); expect(result.error?.statusCode).toBe(500); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/create_dashboard.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/create_gen_ai_dashboard.ts similarity index 87% rename from x-pack/plugins/stack_connectors/server/connector_types/openai/create_dashboard.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/create_gen_ai_dashboard.ts index 80c3bc8cc7f21..665d4258d54d1 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/create_dashboard.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/create_gen_ai_dashboard.ts @@ -8,7 +8,7 @@ import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-ser import { DashboardAttributes } from '@kbn/dashboard-plugin/common'; import { Logger } from '@kbn/logging'; -import { getDashboard } from './dashboard'; +import { getDashboard } from './gen_ai_dashboard'; export interface OutputError { message: string; @@ -19,10 +19,12 @@ export const initDashboard = async ({ logger, savedObjectsClient, dashboardId, + genAIProvider, }: { logger: Logger; savedObjectsClient: SavedObjectsClientContract; dashboardId: string; + genAIProvider: 'OpenAI' | 'Bedrock'; }): Promise<{ success: boolean; error?: OutputError; @@ -50,13 +52,13 @@ export const initDashboard = async ({ try { await savedObjectsClient.create( 'dashboard', - getDashboard(dashboardId).attributes, + getDashboard(genAIProvider, dashboardId).attributes, { overwrite: true, id: dashboardId, } ); - logger.info(`Successfully created Gen Ai Dashboard ${dashboardId}`); + logger.info(`Successfully created Generative AI Dashboard ${dashboardId}`); return { success: true }; } catch (error) { return { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/dashboard.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/gen_ai_dashboard.ts similarity index 90% rename from x-pack/plugins/stack_connectors/server/connector_types/openai/dashboard.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/gen_ai_dashboard.ts index 8503ef9bf59fc..9fd492e1559c9 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/dashboard.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/gen_ai/gen_ai_dashboard.ts @@ -8,10 +8,28 @@ import { DashboardAttributes } from '@kbn/dashboard-plugin/common'; import { v4 as uuidv4 } from 'uuid'; import { SavedObject } from '@kbn/core-saved-objects-common/src/server_types'; +import { OPENAI_TITLE, OPENAI_CONNECTOR_ID } from '../../../../common/openai/constants'; +import { BEDROCK_TITLE, BEDROCK_CONNECTOR_ID } from '../../../../common/bedrock/constants'; -export const dashboardTitle = `OpenAI Token Usage`; +const getDashboardTitle = (title: string) => `${title} Token Usage`; + +export const getDashboard = ( + genAIProvider: 'OpenAI' | 'Bedrock', + dashboardId: string +): SavedObject => { + const attributes = + genAIProvider === 'OpenAI' + ? { + provider: OPENAI_TITLE, + dashboardTitle: getDashboardTitle(OPENAI_TITLE), + actionTypeId: OPENAI_CONNECTOR_ID, + } + : { + provider: BEDROCK_TITLE, + dashboardTitle: getDashboardTitle(BEDROCK_TITLE), + actionTypeId: BEDROCK_CONNECTOR_ID, + }; -export const getDashboard = (dashboardId: string): SavedObject => { const ids: Record = { genAiSavedObjectId: dashboardId, tokens: uuidv4(), @@ -20,10 +38,9 @@ export const getDashboard = (dashboardId: string): SavedObject { }); }); + describe('close incident', () => { + test('it closes an incident with incidentId', async () => { + const res = await api.closeIncident({ + externalService, + params: { + incident: { + externalId: apiParams.incident.externalId, + correlation_id: null, + }, + }, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + }); + }); + + test('it closes an incident with correlation_id', async () => { + const res = await api.closeIncident({ + externalService, + params: { + incident: { + externalId: null, + correlation_id: apiParams.incident.correlation_id, + }, + }, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + }); + }); + + test('it calls closeIncident correctly', async () => { + await api.closeIncident({ + externalService, + params: { + incident: { + externalId: apiParams.incident.externalId, + correlation_id: null, + }, + }, + logger: mockedLogger, + }); + + expect(externalService.closeIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + correlationId: null, + }); + }); + + test('it calls closeIncident correctly with correlation_id', async () => { + await api.closeIncident({ + externalService, + params: { + incident: { + externalId: null, + correlation_id: apiParams.incident.correlation_id, + }, + }, + logger: mockedLogger, + }); + + expect(externalService.closeIncident).toHaveBeenCalledWith({ + incidentId: null, + correlationId: 'ruleId', + }); + }); + }); + describe('getFields', () => { test('it returns the fields correctly', async () => { const res = await api.getFields({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.ts index 88cdfd069cf1b..931f7936bff61 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.ts @@ -16,6 +16,8 @@ import { Incident, PushToServiceApiHandlerArgs, PushToServiceResponse, + CloseIncidentApiHandlerArgs, + ExternalServiceIncidentResponse, } from './types'; const handshakeHandler = async ({ externalService, params }: HandshakeApiHandlerArgs) => {}; @@ -74,6 +76,20 @@ const pushToServiceHandler = async ({ return res; }; +const closeIncidentHandler = async ({ + externalService, + params, +}: CloseIncidentApiHandlerArgs): Promise => { + const { externalId, correlation_id: correlationId } = params.incident; + + const res = await externalService.closeIncident({ + correlationId, + incidentId: externalId, + }); + + return res; +}; + const getFieldsHandler = async ({ externalService, }: GetCommonFieldsHandlerArgs): Promise => { @@ -95,4 +111,5 @@ export const api: ExternalServiceAPI = { getIncident: getIncidentHandler, handshake: handshakeHandler, pushToService: pushToServiceHandler, + closeIncident: closeIncidentHandler, }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts index 1043fe62af1e1..410a5f58ab00b 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts @@ -91,6 +91,16 @@ const createMock = (): jest.Mocked => { description: 'description from servicenow', }) ), + getIncidentByCorrelationId: jest.fn().mockImplementation(() => + Promise.resolve({ + id: 'incident-1', + title: 'INC01', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + short_description: 'title from servicenow', + description: 'description from servicenow', + }) + ), createIncident: jest.fn().mockImplementation(() => Promise.resolve({ id: 'incident-1', @@ -107,6 +117,14 @@ const createMock = (): jest.Mocked => { url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', }) ), + closeIncident: jest.fn().mockImplementation(() => + Promise.resolve({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + }) + ), findIncidents: jest.fn(), getApplicationInformation: jest.fn().mockImplementation(() => Promise.resolve({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts index 5f5ea6ab0ff93..568d9b01e67e6 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts @@ -115,6 +115,13 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ externalId: schema.string(), }); +export const ExecutorSubActionCloseIncidentParamsSchema = schema.object({ + incident: schema.object({ + externalId: schema.nullable(schema.string()), + correlation_id: schema.nullable(schema.string({ defaultValue: DEFAULT_ALERTS_GROUPING_KEY })), + }), +}); + // Reserved for future implementation export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); @@ -144,6 +151,10 @@ export const ExecutorParamsSchemaITSM = schema.oneOf([ subAction: schema.literal('getChoices'), subActionParams: ExecutorSubActionGetChoicesParamsSchema, }), + schema.object({ + subAction: schema.literal('closeIncident'), + subActionParams: ExecutorSubActionCloseIncidentParamsSchema, + }), ]); // Executor parameters for ServiceNow Security Incident Response (SIR) diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts index 28ee0e248c2b8..fcdd0f31b6ec6 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import axios, { AxiosResponse } from 'axios'; +import axios, { AxiosError, AxiosResponse } from 'axios'; import { createExternalService } from './service'; import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; @@ -17,7 +17,10 @@ import { serviceNowCommonFields, serviceNowChoices } from './mocks'; import { snExternalServiceConfig } from './config'; const logger = loggingSystemMock.create().get() as jest.Mocked; -jest.mock('axios'); +jest.mock('axios', () => ({ + create: jest.fn(), + AxiosError: jest.requireActual('axios').AxiosError, +})); jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); return { @@ -73,7 +76,7 @@ const mockImportIncident = (update: boolean) => })); const mockIncidentResponse = (update: boolean) => - requestMock.mockImplementation(() => ({ + requestMock.mockImplementationOnce(() => ({ data: { result: { sys_id: '1', @@ -85,6 +88,19 @@ const mockIncidentResponse = (update: boolean) => }, })); +const mockCorrelationIdIncidentResponse = () => + requestMock.mockImplementationOnce(() => ({ + data: { + result: [ + { + sys_id: '1', + number: 'INC01', + sys_updated_on: '2020-03-10 12:24:20', + }, + ], + }, + })); + const createIncident = async (service: ExternalService) => { // Get application version mockApplicationVersion(); @@ -112,6 +128,35 @@ const updateIncident = async (service: ExternalService) => { }); }; +const closeIncident = async ({ + service, + incidentId, + correlationId, +}: { + service: ExternalService; + incidentId: string | null; + correlationId: string | null; +}) => { + // Get incident response + if (incidentId) { + mockIncidentResponse(false); + } else if (correlationId) { + // get incident by correlationId response + mockCorrelationIdIncidentResponse(); + } + // Get application version + mockApplicationVersion(); + // Import set api response + mockImportIncident(true); + // Get incident response + mockIncidentResponse(true); + + return await service.closeIncident({ + incidentId: incidentId ?? null, + correlationId: correlationId ?? null, + }); +}; + const expectImportedIncident = (update: boolean) => { expect(requestMock).toHaveBeenNthCalledWith(1, { axios, @@ -439,7 +484,7 @@ describe('ServiceNow service', () => { throw new Error('An error has occurred'); }); await expect(service.getIncident('1')).rejects.toThrow( - 'Unable to get incident with id 1. Error: An error has occurred' + '[Action][ServiceNow]: Unable to get incident with id 1. Error: An error has occurred Reason: unknown: errorResponse was null' ); }); @@ -455,6 +500,88 @@ describe('ServiceNow service', () => { }); }); + describe('getIncidentByCorrelationId', () => { + test('it returns the incident correctly', async () => { + requestMock.mockImplementation(() => ({ + data: { result: [{ sys_id: '1', number: 'INC01' }] }, + })); + const res = await service.getIncidentByCorrelationId('custom_correlation_id'); + expect(res).toEqual({ sys_id: '1', number: 'INC01' }); + }); + + test('it should call request with correct arguments', async () => { + requestMock.mockImplementation(() => ({ + data: { result: [{ sys_id: '1', number: 'INC01' }] }, + })); + + await service.getIncidentByCorrelationId('custom_correlation_id'); + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/incident?sysparm_query=ORDERBYDESCsys_created_on^correlation_id=custom_correlation_id', + method: 'get', + }); + }); + + test('it should return null if response is empty', async () => { + requestMock.mockImplementation(() => ({ + data: { result: [] }, + })); + + const res = await service.getIncidentByCorrelationId('custom_correlation_id'); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(res).toBe(null); + }); + + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService({ + credentials: { + config: { apiUrl: 'https://example.com/', isOAuth: false }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities, + serviceConfig: { ...snExternalServiceConfig['.servicenow'], table: 'sn_si_incident' }, + axiosInstance: axios, + }); + + requestMock.mockImplementation(() => ({ + data: { result: [{ sys_id: '1', number: 'INC01' }] }, + })); + + await service.getIncidentByCorrelationId('custom_correlation_id'); + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/sn_si_incident?sysparm_query=ORDERBYDESCsys_created_on^correlation_id=custom_correlation_id', + method: 'get', + }); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementationOnce(() => { + throw new Error('An error has occurred'); + }); + await expect(service.getIncidentByCorrelationId('custom_correlation_id')).rejects.toThrow( + '[Action][ServiceNow]: Unable to get incident by correlation ID custom_correlation_id. Error: An error has occurred Reason: unknown: errorResponse was null' + ); + }); + + test('it should throw an error when instance is not alive', async () => { + requestMock.mockImplementationOnce(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect(service.getIncident('1')).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); + }); + describe('createIncident', () => { // new connectors describe('import set table', () => { @@ -574,6 +701,8 @@ describe('ServiceNow service', () => { test('it creates the incident correctly', async () => { mockIncidentResponse(false); + mockIncidentResponse(false); + const res = await service.createIncident({ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident, }); @@ -608,6 +737,7 @@ describe('ServiceNow service', () => { axiosInstance: axios, }); + mockIncidentResponse(false); mockIncidentResponse(false); const res = await service.createIncident({ @@ -749,6 +879,8 @@ describe('ServiceNow service', () => { test('it updates the incident correctly', async () => { mockIncidentResponse(true); + mockIncidentResponse(true); + const res = await service.updateIncident({ incidentId: '1', incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident, @@ -785,6 +917,7 @@ describe('ServiceNow service', () => { }); mockIncidentResponse(false); + mockIncidentResponse(true); const res = await service.updateIncident({ incidentId: '1', @@ -805,6 +938,311 @@ describe('ServiceNow service', () => { }); }); + describe('closeIncident', () => { + // new connectors + describe('import set table', () => { + test('it closes the incident correctly with incident id', async () => { + const res = await closeIncident({ service, incidentId: '1', correlationId: null }); + + expect(res).toEqual({ + title: 'INC01', + id: '1', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1', + }); + }); + + test('it should call request with correct arguments with incidentId', async () => { + const res = await closeIncident({ service, incidentId: '1', correlationId: null }); + expect(requestMock).toHaveBeenCalledTimes(4); + + expect(requestMock).toHaveBeenNthCalledWith(1, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/incident/1', + method: 'get', + }); + + expect(requestMock).toHaveBeenNthCalledWith(2, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/x_elas2_inc_int/elastic_api/health', + method: 'get', + }); + + expect(requestMock).toHaveBeenNthCalledWith(3, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/import/x_elas2_inc_int_elastic_incident', + method: 'post', + data: { + elastic_incident_id: '1', + u_close_code: 'Closed/Resolved by Caller', + u_state: '7', + u_close_notes: 'Closed by Caller', + }, + }); + + expect(requestMock).toHaveBeenNthCalledWith(4, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/incident/1', + method: 'get', + }); + + expect(res?.url).toEqual('https://example.com/nav_to.do?uri=incident.do?sys_id=1'); + }); + + test('it closes the incident correctly with correlation id', async () => { + const res = await closeIncident({ + service, + incidentId: null, + correlationId: 'custom_correlation_id', + }); + + expect(res).toEqual({ + title: 'INC01', + id: '1', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1', + }); + }); + + test('it should call request with correct arguments with correlationId', async () => { + const res = await closeIncident({ + service, + incidentId: null, + correlationId: 'custom_correlation_id', + }); + + expect(requestMock).toHaveBeenCalledTimes(4); + + expect(requestMock).toHaveBeenNthCalledWith(1, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/incident?sysparm_query=ORDERBYDESCsys_created_on^correlation_id=custom_correlation_id', + method: 'get', + }); + + expect(requestMock).toHaveBeenNthCalledWith(2, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/x_elas2_inc_int/elastic_api/health', + method: 'get', + }); + + expect(requestMock).toHaveBeenNthCalledWith(3, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/import/x_elas2_inc_int_elastic_incident', + method: 'post', + data: { + elastic_incident_id: '1', + u_close_code: 'Closed/Resolved by Caller', + u_state: '7', + u_close_notes: 'Closed by Caller', + }, + }); + + expect(requestMock).toHaveBeenNthCalledWith(4, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/incident/1', + method: 'get', + }); + + expect(res?.url).toEqual('https://example.com/nav_to.do?uri=incident.do?sys_id=1'); + }); + + test('it should throw an error when the incidentId and correlation Id are null', async () => { + await expect( + service.closeIncident({ incidentId: null, correlationId: null }) + ).rejects.toThrow( + '[Action][ServiceNow]: Unable to close incident. Error: No correlationId or incidentId found. Reason: unknown: errorResponse was null' + ); + }); + + test('it should throw an error when the no incidents found with given incidentId ', async () => { + const axiosError = { + message: 'Request failed with status code 404', + response: { status: 404 }, + } as AxiosError; + + requestMock.mockImplementation(() => { + throw axiosError; + }); + + const res = await service.closeIncident({ + incidentId: 'xyz', + correlationId: null, + }); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "[ServiceNow][CloseIncident] No incident found with incidentId: xyz.", + ] + `); + expect(res).toBeNull(); + }); + + test('it should log warning if found incident is closed', async () => { + requestMock.mockImplementationOnce(() => ({ + data: { + result: { + sys_id: '1', + number: 'INC01', + state: '7', + sys_created_on: '2020-03-10 12:24:20', + }, + }, + })); + + await service.closeIncident({ incidentId: '1', correlationId: null }); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "[ServiceNow][CloseIncident] Incident with correlation_id: null or incidentId: 1 is closed.", + ] + `); + }); + + test('it should return null if found incident with correlation id is null', async () => { + requestMock.mockImplementationOnce(() => ({ + data: { + result: [], + }, + })); + + const res = await service.closeIncident({ + incidentId: null, + correlationId: 'bar', + }); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "[ServiceNow][CloseIncident] No incident found with correlation_id: bar or incidentId: null.", + ] + `); + expect(res).toBeNull(); + }); + + test('it should throw an error when instance is not alive', async () => { + mockIncidentResponse(false); + requestMock.mockImplementation(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect( + service.closeIncident({ + incidentId: '1', + correlationId: null, + }) + ).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); + }); + + // old connectors + describe('table API', () => { + beforeEach(() => { + service = createExternalService({ + credentials: { + config: { apiUrl: 'https://example.com/', isOAuth: false }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities, + serviceConfig: { ...snExternalServiceConfig['.servicenow'], useImportAPI: false }, + axiosInstance: axios, + }); + }); + + test('it closes the incident correctly', async () => { + mockIncidentResponse(false); + mockImportIncident(true); + mockIncidentResponse(true); + + const res = await service.closeIncident({ + incidentId: '1', + correlationId: null, + }); + + expect(res).toEqual({ + title: 'INC01', + id: '1', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1', + }); + }); + + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService({ + credentials: { + config: { apiUrl: 'https://example.com/', isOAuth: false }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities, + serviceConfig: { ...snExternalServiceConfig['.servicenow-sir'], useImportAPI: false }, + axiosInstance: axios, + }); + + mockIncidentResponse(false); + mockIncidentResponse(true); + mockIncidentResponse(true); + + const res = await service.closeIncident({ + incidentId: '1', + correlationId: null, + }); + + expect(requestMock).toHaveBeenNthCalledWith(1, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/sn_si_incident/1', + method: 'get', + }); + + expect(requestMock).toHaveBeenNthCalledWith(2, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/sn_si_incident/1', + method: 'patch', + data: { + close_code: 'Closed/Resolved by Caller', + state: '7', + close_notes: 'Closed by Caller', + }, + }); + + expect(requestMock).toHaveBeenNthCalledWith(3, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/v2/table/sn_si_incident/1', + method: 'get', + }); + + expect(res?.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1'); + }); + }); + }); + describe('getFields', () => { test('it should call request with correct arguments', async () => { requestMock.mockImplementation(() => ({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts index 3f1b7cd7cdc64..906c47c962d82 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts @@ -17,6 +17,7 @@ import { ServiceNowIncident, GetApplicationInfoResponse, ServiceFactory, + ExternalServiceParamsClose, } from './types'; import * as i18n from './translations'; @@ -75,6 +76,10 @@ export const createExternalService: ServiceFactory = ({ return `${urlWithoutTrailingSlash}/nav_to.do?uri=${table}.do?sys_id=${id}`; }; + const getIncidentByCorrelationIdUrl = (correlationId: string) => { + return `${tableApiIncidentUrl}?sysparm_query=ORDERBYDESCsys_created_on^correlation_id=${correlationId}`; + }; + const getChoicesURL = (fields: string[]) => { const elements = fields .slice(1) @@ -169,6 +174,7 @@ export const createExternalService: ServiceFactory = ({ params, configurationUtilities, }); + checkInstance(res); return res.data.result.length > 0 ? { ...res.data.result } : undefined; } catch (error) { @@ -249,6 +255,87 @@ export const createExternalService: ServiceFactory = ({ } }; + const getIncidentByCorrelationId = async ( + correlationId: string + ): Promise => { + try { + const res = await request({ + axios: axiosInstance, + url: getIncidentByCorrelationIdUrl(correlationId), + method: 'get', + logger, + configurationUtilities, + }); + + checkInstance(res); + + const foundIncident = res.data.result[0] ?? null; + + return foundIncident; + } catch (error) { + throw createServiceError(error, `Unable to get incident by correlation ID ${correlationId}`); + } + }; + + const closeIncident = async (params: ExternalServiceParamsClose) => { + try { + const { correlationId, incidentId } = params; + let incidentToBeClosed = null; + + if (correlationId == null && incidentId == null) { + throw new Error('No correlationId or incidentId found.'); + } + + if (incidentId) { + incidentToBeClosed = await getIncident(incidentId); + } else if (correlationId) { + incidentToBeClosed = await getIncidentByCorrelationId(correlationId); + } + + if (incidentToBeClosed === null) { + logger.warn( + `[ServiceNow][CloseIncident] No incident found with correlation_id: ${correlationId} or incidentId: ${incidentId}.` + ); + + return null; + } + + if (incidentToBeClosed.state === '7') { + logger.warn( + `[ServiceNow][CloseIncident] Incident with correlation_id: ${correlationId} or incidentId: ${incidentId} is closed.` + ); + + return { + title: incidentToBeClosed.number, + id: incidentToBeClosed.sys_id, + pushedDate: getPushedDate(incidentToBeClosed.sys_updated_on), + url: getIncidentViewURL(incidentToBeClosed.sys_id), + }; + } + + const closedIncident = await updateIncident({ + incidentId: incidentToBeClosed.sys_id, + incident: { + state: '7', // used for "closed" status in serviceNow + close_code: 'Closed/Resolved by Caller', + close_notes: 'Closed by Caller', + }, + }); + + return closedIncident; + } catch (error) { + if (error?.response?.status === 404) { + logger.warn( + `[ServiceNow][CloseIncident] No incident found with incidentId: ${params.incidentId}.` + ); + + return null; + } + + throw createServiceError(error, 'Unable to close incident'); + } + }; + const getFields = async () => { try { const res = await request({ @@ -292,5 +379,7 @@ export const createExternalService: ServiceFactory = ({ checkInstance, getApplicationInformation, checkIfApplicationIsInstalled, + closeIncident, + getIncidentByCorrelationId, }; }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/types.ts index dbd17e67d9500..86d037c324e41 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/types.ts @@ -26,6 +26,7 @@ import { ExecutorParamsSchemaITOM, ExecutorSubActionAddEventParamsSchema, ExternalIncidentServiceConfigurationBaseSchema, + ExecutorSubActionCloseIncidentParamsSchema, } from './schema'; import { SNProductsConfigValue } from '../../../../common/servicenow_config'; @@ -104,17 +105,26 @@ export interface ExternalServiceParamsUpdate { incident: PartialIncident & Record; } +export interface ExternalServiceParamsClose { + incidentId: string | null; + correlationId: string | null; +} + export interface ExternalService { getChoices: (fields: string[]) => Promise; getIncident: (id: string) => Promise; getFields: () => Promise; createIncident: (params: ExternalServiceParamsCreate) => Promise; updateIncident: (params: ExternalServiceParamsUpdate) => Promise; + closeIncident: ( + params: ExternalServiceParamsClose + ) => Promise; findIncidents: (params?: Record) => Promise; getUrl: () => string; checkInstance: (res: AxiosResponse) => void; getApplicationInformation: () => Promise; checkIfApplicationIsInstalled: () => Promise; + getIncidentByCorrelationId: (correlationId: string) => Promise; } export type PushToServiceApiParams = ExecutorSubActionPushParams; @@ -134,6 +144,10 @@ export type ExecutorSubActionHandshakeParams = TypeOf< typeof ExecutorSubActionHandshakeParamsSchema >; +export type ExecutorSubActionCloseIncidentParams = TypeOf< + typeof ExecutorSubActionCloseIncidentParamsSchema +>; + export type ServiceNowITSMIncident = Omit< TypeOf['incident'], 'externalId' @@ -155,6 +169,10 @@ export interface GetIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs params: ExecutorSubActionGetIncidentParams; } +export interface CloseIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs { + params: ExecutorSubActionCloseIncidentParams; +} + export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs { params: ExecutorSubActionHandshakeParams; } @@ -199,6 +217,9 @@ export interface ExternalServiceAPI { handshake: (args: HandshakeApiHandlerArgs) => Promise; pushToService: (args: PushToServiceApiHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; + closeIncident: ( + args: CloseIncidentApiHandlerArgs + ) => Promise; } export interface ExternalServiceCommentResponse { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts index 79ca4a662608c..f151affe8a597 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts @@ -28,6 +28,7 @@ jest.mock('@kbn/actions-plugin/server/lib/get_oauth_jwt_access_token', () => ({ jest.mock('axios', () => ({ create: jest.fn(), AxiosHeaders: jest.requireActual('axios').AxiosHeaders, + AxiosError: jest.requireActual('axios').AxiosError, })); const createAxiosInstanceMock = axios.create as jest.Mock; const axiosInstanceMock = { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts index 2e6ba5327ed2b..44171d1a11947 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import axios, { AxiosHeaders, AxiosInstance, AxiosResponse } from 'axios'; +import axios, { AxiosError, AxiosHeaders, AxiosInstance, AxiosResponse } from 'axios'; import { Logger } from '@kbn/core/server'; import { addTimeZoneToDate, getErrorMessage } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; @@ -42,14 +42,22 @@ const createErrorMessage = (errorResponse?: ServiceNowError): string => { : 'unknown: no error in error response'; }; -export const createServiceError = (error: ResponseError, message: string) => - new Error( +export const createServiceError = (error: ResponseError, message: string): AxiosError => { + const serviceError = new AxiosError( getErrorMessage( i18n.SERVICENOW, `${message}. Error: ${error.message} Reason: ${createErrorMessage(error.response?.data)}` ) ); + serviceError.code = error.code; + serviceError.config = error.config; + serviceError.request = error.request; + serviceError.response = error.response; + + return serviceError; +}; + export const getPushedDate = (timestamp?: string) => { if (timestamp != null) { return new Date(addTimeZoneToDate(timestamp)).toISOString(); 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 c7d6feb6887ad..da7d3ef05f358 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 @@ -16,9 +16,9 @@ import { 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 { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; import { PassThrough, Transform } from 'stream'; -jest.mock('./create_dashboard'); +jest.mock('../lib/gen_ai/create_gen_ai_dashboard'); describe('OpenAIConnector', () => { let mockRequest: jest.Mock; 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 8dfeac0be8502..88ad3a0d3da78 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 @@ -31,7 +31,7 @@ import { InvokeAIActionParams, InvokeAIActionResponse, } from '../../../common/openai/types'; -import { initDashboard } from './create_dashboard'; +import { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; import { getAxiosOptions, getRequestWithStreamOption, @@ -187,6 +187,7 @@ export class OpenAIConnector extends SubActionConnector { logger: this.logger, savedObjectsClient: this.savedObjectsClient, dashboardId, + genAIProvider: 'OpenAI', }); return { available: response.success }; @@ -209,7 +210,7 @@ export class OpenAIConnector extends SubActionConnector { /** * Deprecated. Use invokeStream instead. - * TODO: remove before 8.12 FF in part 3 of streaming work for security solution + * TODO: remove once streaming work is implemented in langchain mode for security solution * tracked here: https://github.com/elastic/security-team/issues/7363 */ public async invokeAI(body: InvokeAIActionParams): Promise { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.test.ts index 876658e3f0ac0..a4cb190a85580 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.test.ts @@ -23,6 +23,7 @@ jest.mock('./api', () => ({ getIncident: jest.fn(), handshake: jest.fn(), pushToService: jest.fn(), + closeIncident: jest.fn(), }, })); @@ -75,6 +76,33 @@ describe('ServiceNow', () => { 'work_notes' ); }); + + test('calls closeIncident sub action correctly', async () => { + const actionId = 'some-action-id'; + const executorOptions = { + actionId, + config, + secrets, + params: { + subAction: 'closeIncident', + subActionParams: { + incident: { + correlationId: 'custom_correlation_id', + externalId: null, + }, + }, + }, + services, + logger: mockedLogger, + } as unknown as ServiceNowConnectorTypeExecutorOptions< + ServiceNowPublicConfigurationType, + ExecutorParams + >; + await connectorType.executor(executorOptions); + expect( + (api.closeIncident as jest.Mock).mock.calls[0][0].params.incident.correlationId + ).toBe('custom_correlation_id'); + }); }); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.ts index ccfdb7810a04d..0322b0e341844 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/index.ts @@ -41,6 +41,7 @@ import { ServiceNowExecutorResultData, ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType, + ExecutorSubActionCloseIncidentParams, } from '../lib/servicenow/types'; import { ServiceNowITSMConnectorTypeId, @@ -102,7 +103,13 @@ export function getServiceNowITSMConnectorType(): ServiceNowConnectorType< } // action executor -const supportedSubActions: string[] = ['getFields', 'pushToService', 'getChoices', 'getIncident']; +const supportedSubActions: string[] = [ + 'getFields', + 'pushToService', + 'getChoices', + 'getIncident', + 'closeIncident', +]; async function executor( { actionTypeId, @@ -173,5 +180,14 @@ async function executor( }); } + if (subAction === 'closeIncident') { + const closeIncidentParams = subActionParams as ExecutorSubActionCloseIncidentParams; + data = await api.closeIncident({ + externalService, + params: closeIncidentParams, + logger, + }); + } + return { status: 'ok', data: data ?? {}, actionId }; } diff --git a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/service.test.ts index dce1d08bceb16..1c068dc60489d 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itsm/service.test.ts @@ -17,7 +17,10 @@ import { serviceNowCommonFields, serviceNowChoices } from '../lib/servicenow/moc import { snExternalServiceConfig } from '../lib/servicenow/config'; const logger = loggingSystemMock.create().get() as jest.Mocked; -jest.mock('axios'); +jest.mock('axios', () => ({ + create: jest.fn(), + AxiosError: jest.requireActual('axios').AxiosError, +})); jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx index 8f03ab6190af5..e9d6e4e650b46 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx @@ -6,13 +6,9 @@ */ import { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import { UnifiedDocViewer, useEsDocSearch } from '@kbn/unified-doc-viewer-plugin/public'; import React, { useState, MouseEvent } from 'react'; -import { useUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public'; -import { buildDataTableRecord } from '@kbn/discover-utils'; -import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; import { i18n } from '@kbn/i18n'; -import { useFetcher } from '@kbn/observability-shared-plugin/public'; -import { DataTableRecord } from '@kbn/discover-utils/src/types'; import { useDateFormat } from '../../../../../hooks/use_date_format'; import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; import { useSyntheticsDataView } from '../../../contexts/synthetics_data_view_context'; @@ -20,35 +16,12 @@ import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; import { Ping } from '../../../../../../common/runtime_types'; export const ViewDocument = ({ ping }: { ping: Ping }) => { - const { data } = useUnifiedDocViewerServices(); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const dataView = useSyntheticsDataView(); const formatter = useDateFormat(); - const { data: hit } = useFetcher>(async () => { - if (!dataView?.id || !isFlyoutVisible) return; - const response = await data.search - .search({ - params: { - index: SYNTHETICS_INDEX_PATTERN, - body: { - query: { - ids: { - values: [ping.docId], - }, - }, - fields: ['*'], - _source: false, - }, - }, - }) - .toPromise(); - const docs = response?.rawResponse?.hits?.hits ?? []; - if (docs.length > 0) { - return buildDataTableRecord(docs[0], dataView); - } - }, [data, dataView, ping.docId, isFlyoutVisible]); + const [, hit] = useEsDocSearch({ id: ping.docId, index: SYNTHETICS_INDEX_PATTERN, dataView }); return ( <> diff --git a/x-pack/plugins/synthetics/tsconfig.json b/x-pack/plugins/synthetics/tsconfig.json index 4492a20771d8c..2fff5e08016f9 100644 --- a/x-pack/plugins/synthetics/tsconfig.json +++ b/x-pack/plugins/synthetics/tsconfig.json @@ -78,7 +78,6 @@ "@kbn/shared-ux-page-kibana-template", "@kbn/observability-ai-assistant-plugin", "@kbn/unified-doc-viewer-plugin", - "@kbn/discover-utils", "@kbn/shared-ux-link-redirect-app" ], "exclude": [ diff --git a/x-pack/plugins/task_manager/server/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts index 10e440184ab2e..5fb1325da3df9 100644 --- a/x-pack/plugins/task_manager/server/task_pool.test.ts +++ b/x-pack/plugins/task_manager/server/task_pool.test.ts @@ -392,6 +392,43 @@ describe('TaskPool', () => { expect(shouldNotRun).not.toHaveBeenCalled(); }); + // This test is from https://github.com/elastic/kibana/issues/172116 + // It's not clear how to reproduce the actual error, but it is easy to + // reproduce with the wacky test below. It does log the exact error + // from that issue, without the corresponding fix in task_pool.ts + test('works when available workers is 0 but there are tasks to run', async () => { + const logger = loggingSystemMock.create().get(); + const pool = new TaskPool({ + maxWorkers$: of(2), + logger, + }); + + const shouldRun = mockRun(); + + const taskId = uuidv4(); + const task1 = mockTask({ id: taskId, run: shouldRun }); + + // we need to alternate the values of `availableWorkers`. First it + // should be 0, then 1, then 0, then 1, etc. This will cause task_pool.run + // to partition tasks (0 to run, everything as leftover), then at the + // end of run(), to check if it should recurse, it should be > 0. + let awValue = 1; + Object.defineProperty(pool, 'availableWorkers', { + get() { + return ++awValue % 2; + }, + }); + + const result = await pool.run([task1]); + expect(result).toBe(TaskPoolRunResult.RanOutOfCapacity); + + expect((logger as jest.Mocked).warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "task pool run attempts exceeded 3; assuming ran out of capacity; availableWorkers: 0, tasksToRun: 0, leftOverTasks: 1, maxWorkers: 2, occupiedWorkers: 0, workerLoad: 0", + ] + `); + }); + function mockRun() { return jest.fn(async () => { await sleep(0); diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts index 11b79b6d570dc..a3a047cd735c9 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool.ts @@ -34,6 +34,7 @@ export enum TaskPoolRunResult { } const VERSION_CONFLICT_MESSAGE = 'Task has been claimed by another Kibana service'; +const MAX_RUN_ATTEMPTS = 3; /** * Runs tasks in batches, taking costs into account. @@ -107,8 +108,27 @@ export class TaskPool { * @param {TaskRunner[]} tasks * @returns {Promise} */ - public run = async (tasks: TaskRunner[]): Promise => { - const [tasksToRun, leftOverTasks] = partitionListByCount(tasks, this.availableWorkers); + public async run(tasks: TaskRunner[], attempt = 1): Promise { + // Note `this.availableWorkers` is a getter with side effects, so we just want + // to call it once for this bit of the code. + const availableWorkers = this.availableWorkers; + const [tasksToRun, leftOverTasks] = partitionListByCount(tasks, availableWorkers); + + if (attempt > MAX_RUN_ATTEMPTS) { + const stats = [ + `availableWorkers: ${availableWorkers}`, + `tasksToRun: ${tasksToRun.length}`, + `leftOverTasks: ${leftOverTasks.length}`, + `maxWorkers: ${this.maxWorkers}`, + `occupiedWorkers: ${this.occupiedWorkers}`, + `workerLoad: ${this.workerLoad}`, + ].join(', '); + this.logger.warn( + `task pool run attempts exceeded ${MAX_RUN_ATTEMPTS}; assuming ran out of capacity; ${stats}` + ); + return TaskPoolRunResult.RanOutOfCapacity; + } + if (tasksToRun.length) { await Promise.all( tasksToRun @@ -144,14 +164,14 @@ export class TaskPool { if (leftOverTasks.length) { if (this.availableWorkers) { - return this.run(leftOverTasks); + return this.run(leftOverTasks, attempt + 1); } return TaskPoolRunResult.RanOutOfCapacity; } else if (!this.availableWorkers) { return TaskPoolRunResult.RunningAtCapacity; } return TaskPoolRunResult.RunningAllClaimedTasks; - }; + } public cancelRunningTasks() { this.logger.debug('Cancelling running tasks.'); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 00d0dc81c84f8..ae9a3eda79a65 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -8239,7 +8239,6 @@ "xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrences} occ.", "xpack.apm.serviceOverview.embeddedMap.error.toastDescription": "L'usine incorporable ayant l'ID \"{embeddableFactoryId}\" est introuvable.", "xpack.apm.serviceOverview.embeddedMap.subtitle": "Carte affichant le nombre total de {currentMap} en fonction du pays et de la région", - "xpack.apm.serviceOverview.mobileCallOutText": "Il s'agit d'un service mobile, qui est actuellement disponible en tant que version d'évaluation technique. Vous pouvez nous aider à améliorer l'expérience en nous envoyant des commentaires. {feedbackLink}.", "xpack.apm.servicesTable.environmentCount": "{environmentCount, plural, one {1 environnement} many {# environnements} other {# environnements}}", "xpack.apm.settings.agentKeys.apiKeysDisabledErrorDescription": "Contactez votre administrateur système et reportez-vous à {link} pour activer les clés d'API.", "xpack.apm.settings.agentKeys.copyAgentKeyField.title": "Clé \"{name}\" créée", @@ -9004,7 +9003,6 @@ "xpack.apm.mobile.filters.device": "Appareil", "xpack.apm.mobile.filters.nct": "NCT", "xpack.apm.mobile.filters.osVersion": "Version du système d'exploitation", - "xpack.apm.mobile.location.metrics.crashes": "La plupart des pannes", "xpack.apm.mobile.location.metrics.http.requests.title": "Le plus utilisé dans", "xpack.apm.mobile.location.metrics.launches": "La plupart des lancements", "xpack.apm.mobile.location.metrics.sessions": "La plupart des sessions", @@ -9389,8 +9387,6 @@ "xpack.apm.serviceOverview.latencyColumnP95Label": "Latence (95e)", "xpack.apm.serviceOverview.latencyColumnP99Label": "Latence (99e)", "xpack.apm.serviceOverview.loadingText": "Chargement…", - "xpack.apm.serviceOverview.mobileCallOutLink": "Donner un retour", - "xpack.apm.serviceOverview.mobileCallOutTitle": "APM mobile", "xpack.apm.serviceOverview.mostUsedTitle": "Le plus utilisé", "xpack.apm.serviceOverview.noResultsText": "Aucune instance trouvée", "xpack.apm.serviceOverview.throughtputChartTitle": "Rendement", @@ -38623,7 +38619,6 @@ "xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "Modèle par défaut", "xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "Si une requête ne comprend pas de modèle, le modèle par défaut est utilisé.", "xpack.stackConnectors.components.genAi.documentation": "documentation", - "xpack.stackConnectors.components.genAi.error.dashboardApiError": "Une erreur s'est produite lors de la recherche du tableau de bord de l'utilisation des tokens d'OpenAI.", "xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "Un fournisseur d’API est nécessaire.", "xpack.stackConnectors.components.genAi.error.requiredGenerativeAiBodyText": "Le corps est requis.", "xpack.stackConnectors.components.genAi.openAi": "OpenAI", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4f1062ffad98e..2dd045efbc762 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8254,7 +8254,6 @@ "xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrences}件。", "xpack.apm.serviceOverview.embeddedMap.error.toastDescription": "id {embeddableFactoryId}の埋め込み可能ファクトリが見つかりました。", "xpack.apm.serviceOverview.embeddedMap.subtitle": "国と地域別に基づく{currentMap}の総数を示した地図", - "xpack.apm.serviceOverview.mobileCallOutText": "これはモバイルサービスであり、現在はテクニカルプレビューとしてリリースされています。フィードバックを送信して、エクスペリエンスの改善にご協力ください。{feedbackLink}", "xpack.apm.servicesTable.environmentCount": "{environmentCount, plural, other {#個の環境}}", "xpack.apm.settings.agentKeys.apiKeysDisabledErrorDescription": "システム管理者に連絡し、{link}を伝えてAPIキーを有効にしてください。", "xpack.apm.settings.agentKeys.copyAgentKeyField.title": "\"{name}\"キーを作成しました", @@ -9019,7 +9018,6 @@ "xpack.apm.mobile.filters.device": "デバイス", "xpack.apm.mobile.filters.nct": "NCT", "xpack.apm.mobile.filters.osVersion": "OSバージョン", - "xpack.apm.mobile.location.metrics.crashes": "最も多いクラッシュ", "xpack.apm.mobile.location.metrics.http.requests.title": "最も使用されている", "xpack.apm.mobile.location.metrics.launches": "最も多い起動", "xpack.apm.mobile.location.metrics.sessions": "最も多いセッション", @@ -9403,8 +9401,6 @@ "xpack.apm.serviceOverview.latencyColumnP95Label": "レイテンシ(95 番目)", "xpack.apm.serviceOverview.latencyColumnP99Label": "レイテンシ(99 番目)", "xpack.apm.serviceOverview.loadingText": "読み込み中…", - "xpack.apm.serviceOverview.mobileCallOutLink": "フィードバックを作成する", - "xpack.apm.serviceOverview.mobileCallOutTitle": "モバイルAPM", "xpack.apm.serviceOverview.mostUsedTitle": "最も使用されている", "xpack.apm.serviceOverview.noResultsText": "インスタンスが見つかりません", "xpack.apm.serviceOverview.throughtputChartTitle": "スループット", @@ -38622,7 +38618,6 @@ "xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "デフォルトモデル", "xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "リクエストにモデルが含まれていない場合、デフォルトが使われます。", "xpack.stackConnectors.components.genAi.documentation": "ドキュメンテーション", - "xpack.stackConnectors.components.genAi.error.dashboardApiError": "OpenAIトークン使用状況ダッシュボードの検索エラー。", "xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "APIプロバイダーは必須です。", "xpack.stackConnectors.components.genAi.error.requiredGenerativeAiBodyText": "本文が必要です。", "xpack.stackConnectors.components.genAi.openAi": "OpenAI", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e11fef53fcc85..11719afaa2a34 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8253,7 +8253,6 @@ "xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrences} 次", "xpack.apm.serviceOverview.embeddedMap.error.toastDescription": "未找到 ID 为“{embeddableFactoryId}”的可嵌入工厂。", "xpack.apm.serviceOverview.embeddedMap.subtitle": "根据国家和区域显示 {currentMap} 总数的地图", - "xpack.apm.serviceOverview.mobileCallOutText": "这是一项移动服务,它当前以技术预览的形式发布。您可以通过提供反馈来帮助我们改进体验。{feedbackLink}。", "xpack.apm.servicesTable.environmentCount": "{environmentCount, plural, other {# 个环境}}", "xpack.apm.settings.agentKeys.apiKeysDisabledErrorDescription": "请联系您的系统管理员并参阅{link}以启用 API 密钥。", "xpack.apm.settings.agentKeys.copyAgentKeyField.title": "已创建“{name}”密钥", @@ -9018,7 +9017,6 @@ "xpack.apm.mobile.filters.device": "设备", "xpack.apm.mobile.filters.nct": "NCT", "xpack.apm.mobile.filters.osVersion": "操作系统版本", - "xpack.apm.mobile.location.metrics.crashes": "大多数崩溃", "xpack.apm.mobile.location.metrics.http.requests.title": "最常用于", "xpack.apm.mobile.location.metrics.launches": "大多数启动", "xpack.apm.mobile.location.metrics.sessions": "大多数会话", @@ -9403,8 +9401,6 @@ "xpack.apm.serviceOverview.latencyColumnP95Label": "延迟(第 95 个)", "xpack.apm.serviceOverview.latencyColumnP99Label": "延迟(第 99 个)", "xpack.apm.serviceOverview.loadingText": "正在加载……", - "xpack.apm.serviceOverview.mobileCallOutLink": "反馈", - "xpack.apm.serviceOverview.mobileCallOutTitle": "移动 APM", "xpack.apm.serviceOverview.mostUsedTitle": "最常用", "xpack.apm.serviceOverview.noResultsText": "未找到实例", "xpack.apm.serviceOverview.throughtputChartTitle": "吞吐量", @@ -38615,7 +38611,6 @@ "xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "默认模型", "xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "如果请求不包含模型,它将使用默认值。", "xpack.stackConnectors.components.genAi.documentation": "文档", - "xpack.stackConnectors.components.genAi.error.dashboardApiError": "查找 OpenAI 令牌使用情况仪表板时出错。", "xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "“API 提供商”必填。", "xpack.stackConnectors.components.genAi.error.requiredGenerativeAiBodyText": "“正文”必填。", "xpack.stackConnectors.components.genAi.openAi": "OpenAI", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index a9f49f609e13d..dfe48ef86612c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -549,6 +549,7 @@ export const ActionTypeForm = ({ actionParams={actionItem.params as any} errors={actionParamsErrors.errors} index={index} + selectedActionGroupId={selectedActionGroup?.id} editAction={(key: string, value: RuleActionParam, i: number) => { setWarning( validateParamsForWarnings( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx index 2c98853b3c4e6..4d46f8b4741cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx @@ -159,6 +159,11 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab options: props.alertsTableConfiguration.useActionsColumn, }); + const renderCellContext = props.alertsTableConfiguration.useFetchPageContext?.({ + alerts, + columns: props.columns, + }); + const { isBulkActionsColumnActive, getBulkActionsLeadingControlColumn, @@ -373,9 +378,10 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab props.alertsTableConfiguration?.getRenderCellValue ? props.alertsTableConfiguration?.getRenderCellValue({ setFlyoutAlert: handleFlyoutAlert, + context: renderCellContext, }) : basicRenderCellValue, - [handleFlyoutAlert, props.alertsTableConfiguration] + [handleFlyoutAlert, props.alertsTableConfiguration, renderCellContext] )(); const handleRenderCellValue = useCallback( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx index 71bd02d60126f..7ba49502a38e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx @@ -392,6 +392,10 @@ describe('AlertsTable.BulkActions', () => { field: 'kibana.alert.workflow_tags', value: [], }, + { + field: 'kibana.alert.workflow_assignee_ids', + value: [], + }, ], ecs: { _id: 'alert0', @@ -639,6 +643,10 @@ describe('AlertsTable.BulkActions', () => { field: 'kibana.alert.workflow_tags', value: [], }, + { + field: 'kibana.alert.workflow_assignee_ids', + value: [], + }, ], ecs: { _id: 'alert1', @@ -867,6 +875,10 @@ describe('AlertsTable.BulkActions', () => { field: 'kibana.alert.workflow_tags', value: [], }, + { + field: 'kibana.alert.workflow_assignee_ids', + value: [], + }, ], ecs: { _id: 'alert0', @@ -893,6 +905,10 @@ describe('AlertsTable.BulkActions', () => { field: 'kibana.alert.workflow_tags', value: [], }, + { + field: 'kibana.alert.workflow_assignee_ids', + value: [], + }, ], ecs: { _id: 'alert1', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/toolbar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/toolbar.tsx index f75dbc43c1fe0..ef3ba30e12082 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/toolbar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/toolbar.tsx @@ -13,6 +13,7 @@ import { ALERT_CASE_IDS, ALERT_RULE_NAME, ALERT_RULE_UUID, + ALERT_WORKFLOW_ASSIGNEE_IDS, ALERT_WORKFLOW_TAGS, } from '@kbn/rule-data-utils'; import { @@ -64,6 +65,7 @@ const selectedIdsToTimelineItemMapper = ( { field: ALERT_RULE_UUID, value: alert[ALERT_RULE_UUID] }, { field: ALERT_CASE_IDS, value: alert[ALERT_CASE_IDS] ?? [] }, { field: ALERT_WORKFLOW_TAGS, value: alert[ALERT_WORKFLOW_TAGS] ?? [] }, + { field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: alert[ALERT_WORKFLOW_ASSIGNEE_IDS] ?? [] }, ], ecs: { _id: alert._id, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index bc0ce10d461e6..4b04e20954610 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -224,6 +224,7 @@ export interface ActionParamsProps { actionConnector?: ActionConnector; isLoading?: boolean; isDisabled?: boolean; + selectedActionGroupId?: string; showEmailSubjectAndMessage?: boolean; executionMode?: ActionConnectorMode; onBlur?: (field?: string) => void; @@ -575,12 +576,22 @@ export type AlertsTableProps = { } & Partial>; // TODO We need to create generic type between our plugin, right now we have different one because of the old alerts table -export type GetRenderCellValue = ({ +export type GetRenderCellValue = ({ setFlyoutAlert, + context, }: { setFlyoutAlert?: (data: unknown) => void; + context?: T; }) => (props: unknown) => React.ReactNode; +export type PreFetchPageContext = ({ + alerts, + columns, +}: { + alerts: Alerts; + columns: EuiDataGridColumn[]; +}) => T; + export type AlertTableFlyoutComponent = | React.FunctionComponent | React.LazyExoticComponent> @@ -699,6 +710,7 @@ export interface AlertsTableConfigurationRegistry { }; useFieldBrowserOptions?: UseFieldBrowserOptions; showInspectButton?: boolean; + useFetchPageContext?: PreFetchPageContext; } export interface AlertsTableConfigurationRegistryWithActions 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 60eb8b6634a35..2e765d32ea9cc 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 @@ -33,7 +33,9 @@ const defaultConfig = { export default function bedrockTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const objectRemover = new ObjectRemover(supertest); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const configService = getService('config'); + const retry = getService('retry'); const createConnector = async (apiUrl: string, spaceId?: string) => { const result = await supertest .post(`${getUrlPrefix(spaceId ?? 'default')}/api/actions/connector`) @@ -446,6 +448,68 @@ export default function bedrockTest({ getService }: FtrProviderContext) { }); }); }); + + describe('Token tracking dashboard', () => { + const dashboardId = 'specific-dashboard-id-default'; + + it('should not create a dashboard when user does not have kibana event log permissions', async () => { + const { body } = await supertestWithoutAuth + .post(`/api/actions/connector/${bedrockActionId}/_execute`) + .auth('global_read', 'global_read-password') + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'getDashboard', + subActionParams: { + dashboardId, + }, + }, + }) + .expect(200); + + // check dashboard has not been created + await supertest + .get(`/api/saved_objects/dashboard/${dashboardId}`) + .set('kbn-xsrf', 'foo') + .expect(404); + expect(body).to.eql({ + status: 'ok', + connector_id: bedrockActionId, + data: { available: false }, + }); + }); + + it('should create a dashboard when user has correct permissions', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${bedrockActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'getDashboard', + subActionParams: { + dashboardId, + }, + }, + }) + .expect(200); + + // check dashboard has been created + await retry.try(async () => + supertest + .get(`/api/saved_objects/dashboard/${dashboardId}`) + .set('kbn-xsrf', 'foo') + .expect(200) + ); + + objectRemover.add('default', dashboardId, 'dashboard', 'saved_objects'); + + expect(body).to.eql({ + status: 'ok', + connector_id: bedrockActionId, + data: { available: true }, + }); + }); + }); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/openai.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/openai.ts index f13f9f839349c..a0a906f0fa2ad 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/openai.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/openai.ts @@ -313,7 +313,7 @@ export default function genAiTest({ getService }: FtrProviderContext) { data: genAiSuccessResponse, }); }); - describe('OpenAI dashboard', () => { + describe('Token tracking dashboard', () => { const dashboardId = 'specific-dashboard-id-default'; it('should not create a dashboard when user does not have kibana event log permissions', async () => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts index 96f0694ef794b..3525597313514 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts @@ -466,7 +466,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [getChoices]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [getChoices]\n- [5.subAction]: expected value to equal [closeIncident]', }); }); }); @@ -484,7 +484,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [getChoices]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [getChoices]\n- [5.subAction]: expected value to equal [closeIncident]', }); }); }); @@ -507,7 +507,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [getChoices]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [getChoices]\n- [5.subAction]: expected value to equal [closeIncident]', }); }); }); @@ -534,7 +534,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [getChoices]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [getChoices]\n- [5.subAction]: expected value to equal [closeIncident]', }); }); }); @@ -561,7 +561,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [getChoices]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [getChoices]\n- [5.subAction]: expected value to equal [closeIncident]', }); }); }); @@ -583,7 +583,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subActionParams.fields]: expected value of type [array] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subActionParams.fields]: expected value of type [array] but got [undefined]\n- [5.subAction]: expected value to equal [closeIncident]', }); }); }); @@ -716,6 +716,32 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { }); }); }); + + describe('closeIncident', () => { + it('should close the incident', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'closeIncident', + subActionParams: { + incident: { + correlation_id: 'custom_correlation_id', + }, + }, + }, + }) + .expect(200); + + expect(proxyHaveBeenCalled).to.equal(true); + expect(result).to.eql({ + status: 'ok', + connector_id: simulatedActionId, + data: {}, + }); + }); + }); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts new file mode 100644 index 0000000000000..d55967ac7092a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts @@ -0,0 +1,156 @@ +/* + * 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 expect from '@kbn/expect'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +type ErrorGroups = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['errorGroups']; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-swift'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics', + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.errorGroups).to.empty(); + }); + }); + + registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { + describe('errors group', () => { + const appleTransaction = { + name: 'GET /apple 🍎 ', + successRate: 75, + failureRate: 25, + }; + + const bananaTransaction = { + name: 'GET /banana 🍌', + successRate: 50, + failureRate: 50, + }; + + before(async () => { + const serviceInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'swift' }) + .instance('instance-a'); + + await synthtraceEsClient.index([ + timerange(start, end) + .interval('1m') + .rate(appleTransaction.successRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: appleTransaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .interval('1m') + .rate(appleTransaction.failureRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: appleTransaction.name }) + .errors( + serviceInstance + .crash({ + message: 'crash 1', + }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + timerange(start, end) + .interval('1m') + .rate(bananaTransaction.successRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: bananaTransaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .interval('1m') + .rate(bananaTransaction.failureRate) + .generator((timestamp) => + serviceInstance + .transaction({ transactionName: bananaTransaction.name }) + .errors( + serviceInstance + .crash({ + message: 'crash 2', + }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + ]); + }); + + after(() => synthtraceEsClient.clean()); + + describe('returns the correct data', () => { + let errorGroups: ErrorGroups; + before(async () => { + const response = await callApi(); + errorGroups = response.body.errorGroups; + }); + it('returns correct number of crashes', () => { + expect(errorGroups.length).to.equal(2); + expect(errorGroups.map((error) => error.name).sort()).to.eql(['crash 1', 'crash 2']); + }); + + it('returns correct occurrences', () => { + const numberOfBuckets = 15; + expect(errorGroups.map((error) => error.occurrences).sort()).to.eql([ + appleTransaction.failureRate * numberOfBuckets, + bananaTransaction.failureRate * numberOfBuckets, + ]); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts new file mode 100644 index 0000000000000..b3a553bf980c7 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts @@ -0,0 +1,202 @@ +/* + * 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 expect from '@kbn/expect'; +import { first, last, sumBy } from 'lodash'; +import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, generateData } from './generate_data'; + +type ErrorsDistribution = + APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-swift'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/mobile-services/{serviceName}/crashes/distribution', + params: { + path: { + serviceName, + ...overrides?.path, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + return response; + } + + registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.currentPeriod.length).to.be(0); + expect(response.body.previousPeriod.length).to.be(0); + }); + }); + + registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { + describe('errors distribution', () => { + const { appleTransaction, bananaTransaction } = config; + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('without comparison', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi(); + errorsDistribution = response.body; + }); + + it('displays combined number of occurrences', () => { + const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); + const numberOfBuckets = 15; + expect(countSum).to.equal( + (appleTransaction.failureRate + bananaTransaction.failureRate) * numberOfBuckets + ); + }); + + describe('displays correct start in errors distribution chart', () => { + let errorsDistributionWithComparison: ErrorsDistribution; + before(async () => { + const responseWithComparison = await callApi({ + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + offset: '15m', + }, + }); + errorsDistributionWithComparison = responseWithComparison.body; + }); + it('has same start time when comparison is enabled', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistributionWithComparison.currentPeriod)?.x + ); + }); + }); + }); + + describe('displays occurrences for type "apple transaction" only', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + query: { kuery: `error.exception.type:"${appleTransaction.name}"` }, + }); + errorsDistribution = response.body; + }); + it('displays combined number of occurrences', () => { + const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); + const numberOfBuckets = 15; + expect(countSum).to.equal(appleTransaction.failureRate * numberOfBuckets); + }); + }); + + describe('with comparison', () => { + describe('when data is returned', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const fiveMinutes = 5 * 60 * 1000; + const response = await callApi({ + query: { + start: new Date(end - fiveMinutes).toISOString(), + end: new Date(end).toISOString(), + offset: '5m', + }, + }); + errorsDistribution = response.body; + }); + it('returns some data', () => { + const hasCurrentPeriodData = errorsDistribution.currentPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + const hasPreviousPeriodData = errorsDistribution.previousPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); + + it('has same start time for both periods', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistribution.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(errorsDistribution.currentPeriod)?.x).to.equal( + last(errorsDistribution.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(errorsDistribution.currentPeriod.length).to.equal( + errorsDistribution.previousPeriod.length + ); + }); + }); + + describe('when no data is returned', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + query: { + start: '2021-01-03T00:00:00.000Z', + end: '2021-01-03T00:15:00.000Z', + offset: '1d', + }, + }); + errorsDistribution = response.body; + }); + + it('has same start time for both periods', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistribution.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(errorsDistribution.currentPeriod)?.x).to.equal( + last(errorsDistribution.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(errorsDistribution.currentPeriod.length).to.equal( + errorsDistribution.previousPeriod.length + ); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts new file mode 100644 index 0000000000000..606d97fb9ce04 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; + +export const config = { + appleTransaction: { + name: 'GET /apple 🍎 ', + successRate: 75, + failureRate: 25, + }, + bananaTransaction: { + name: 'GET /banana 🍌', + successRate: 50, + failureRate: 50, + }, +}; + +export async function generateData({ + synthtraceEsClient, + serviceName, + start, + end, +}: { + synthtraceEsClient: ApmSynthtraceEsClient; + serviceName: string; + start: number; + end: number; +}) { + const servicesSwiftProdInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'swift' }) + .instance('instance-a'); + + const interval = '1m'; + + const { bananaTransaction, appleTransaction } = config; + + const documents = [appleTransaction, bananaTransaction].flatMap((transaction, index) => { + return [ + timerange(start, end) + .interval(interval) + .rate(transaction.successRate) + .generator((timestamp) => + servicesSwiftProdInstance + .transaction({ transactionName: transaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .interval(interval) + .rate(transaction.failureRate) + .generator((timestamp) => + servicesSwiftProdInstance + .transaction({ transactionName: transaction.name }) + .errors( + servicesSwiftProdInstance + .crash({ + message: `Error ${index}`, + type: transaction.name, + }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + ]; + }); + + await synthtraceEsClient.index(documents); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts b/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts new file mode 100644 index 0000000000000..663849f274adb --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts @@ -0,0 +1,73 @@ +/* + * 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 { apm, timerange } from '@kbn/apm-synthtrace-client'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; + +export const config = { + appleTransaction: { + name: 'GET /apple 🍎 ', + successRate: 75, + failureRate: 25, + }, + bananaTransaction: { + name: 'GET /banana 🍌', + successRate: 50, + failureRate: 50, + }, +}; + +export async function generateData({ + synthtraceEsClient, + serviceName, + start, + end, +}: { + synthtraceEsClient: ApmSynthtraceEsClient; + serviceName: string; + start: number; + end: number; +}) { + const serviceGoProdInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'swift' }) + .instance('instance-a'); + + const interval = '1m'; + + const { bananaTransaction, appleTransaction } = config; + + const documents = [appleTransaction, bananaTransaction].flatMap((transaction, index) => { + return [ + timerange(start, end) + .interval(interval) + .rate(transaction.successRate) + .generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: transaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .interval(interval) + .rate(transaction.failureRate) + .generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: transaction.name }) + .errors( + serviceGoProdInstance + .error({ message: `Error ${index}`, type: transaction.name }) + .timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + ]; + }); + + await synthtraceEsClient.index(documents); +} diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts new file mode 100644 index 0000000000000..07e4d5f7ca02d --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts @@ -0,0 +1,187 @@ +/* + * 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 expect from '@kbn/expect'; +import { timerange } from '@kbn/apm-synthtrace-client'; +import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service'; +import { orderBy } from 'lodash'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, generateData } from './generate_data'; + +type ErrorGroupSamples = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples'>; + +type ErrorSampleDetails = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callErrorGroupSamplesApi({ groupId }: { groupId: string }) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/samples', + params: { + path: { + serviceName, + groupId, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + return response; + } + + async function callErrorSampleDetailsApi(errorId: string) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}', + params: { + path: { + serviceName, + groupId: 'foo', + errorId, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + return response; + } + + registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await callErrorGroupSamplesApi({ groupId: 'foo' }); + expect(response.status).to.be(200); + expect(response.body.occurrencesCount).to.be(0); + }); + }); + + registry.when('when samples data is loaded', { config: 'basic', archives: [] }, () => { + const { bananaTransaction } = config; + describe('error group id', () => { + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('return correct data', () => { + let errorsSamplesResponse: ErrorGroupSamples; + before(async () => { + const response = await callErrorGroupSamplesApi({ + groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03', + }); + errorsSamplesResponse = response.body; + }); + + it('displays correct number of occurrences', () => { + const numberOfBuckets = 15; + expect(errorsSamplesResponse.occurrencesCount).to.equal( + bananaTransaction.failureRate * numberOfBuckets + ); + }); + }); + }); + }); + + registry.when('when error sample data is loaded', { config: 'basic', archives: [] }, () => { + describe('error sample id', () => { + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('return correct data', () => { + let errorSampleDetailsResponse: ErrorSampleDetails; + before(async () => { + const errorsSamplesResponse = await callErrorGroupSamplesApi({ + groupId: '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03', + }); + + const errorId = errorsSamplesResponse.body.errorSampleIds[0]; + + const response = await callErrorSampleDetailsApi(errorId); + errorSampleDetailsResponse = response.body; + }); + + it('displays correct error grouping_key', () => { + expect(errorSampleDetailsResponse.error.error.grouping_key).to.equal( + '98b75903135eac35ad42419bd3b45cf8b4270c61cbd0ede0f7e8c8a9ac9fdb03' + ); + }); + + it('displays correct error message', () => { + expect(errorSampleDetailsResponse.error.error.exception?.[0].message).to.equal('Error 1'); + }); + }); + }); + + describe('with sampled and unsampled transactions', () => { + let errorGroupSamplesResponse: ErrorGroupSamples; + + before(async () => { + const instance = service(serviceName, 'production', 'go').instance('a'); + const errorMessage = 'Error 1'; + const groupId = getErrorGroupingKey(errorMessage); + + await synthtraceEsClient.index([ + timerange(start, end) + .interval('15m') + .rate(1) + .generator((timestamp) => { + return [ + instance + .transaction('GET /api/foo') + .duration(100) + .timestamp(timestamp) + .sample(false) + .errors( + instance.error({ message: errorMessage }).timestamp(timestamp), + instance.error({ message: errorMessage }).timestamp(timestamp + 1) + ), + instance + .transaction('GET /api/foo') + .duration(100) + .timestamp(timestamp) + .sample(true) + .errors(instance.error({ message: errorMessage }).timestamp(timestamp)), + ]; + }), + ]); + + errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body; + }); + + after(() => synthtraceEsClient.clean()); + + it('returns the errors in the correct order (sampled first, then unsampled)', () => { + const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => parseInt(id, 10)); + + // this checks whether the order of indexing is different from the order that is returned + // if it is not, scoring/sorting is broken + expect(errorGroupSamplesResponse.errorSampleIds.length).to.be(3); + expect(idsOfErrors).to.not.eql(orderBy(idsOfErrors)); + }); + }); + }); +} diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts b/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts index 173630e56837e..2939f3eed9266 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts @@ -17,13 +17,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'findings', 'header']); const chance = new Chance(); + const cspmResourceId = chance.guid(); + const cspmResourceName = 'gcp-resource'; + const cspmResourceSubType = 'gcp-monitoring'; + // We need to use a dataset for the tests to run // We intentionally make some fields start with a capital letter to test that the query bar is case-insensitive/case-sensitive const data = [ { '@timestamp': new Date().toISOString(), resource: { id: chance.guid(), name: `kubelet`, sub_type: 'lower case sub type' }, - result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' }, + result: { evaluation: 'failed' }, orchestrator: { cluster: { id: '1', @@ -41,16 +45,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, type: 'process', }, - cluster_id: 'Upper case cluster id', }, { '@timestamp': new Date().toISOString(), resource: { id: chance.guid(), name: `Pod`, sub_type: 'Upper case sub type' }, - result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' }, - cloud: { - account: { + result: { evaluation: 'passed' }, + orchestrator: { + cluster: { id: '1', - name: 'Account 1', + name: 'Cluster 2', }, }, rule: { @@ -64,11 +67,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, type: 'process', }, - cluster_id: 'Another Upper case cluster id', }, { '@timestamp': new Date().toISOString(), - resource: { id: chance.guid(), name: `process`, sub_type: 'another lower case type' }, + resource: { id: cspmResourceId, name: cspmResourceName, sub_type: cspmResourceSubType }, result: { evaluation: 'passed' }, cloud: { account: { @@ -80,18 +82,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { name: 'Another upper case rule name', section: 'lower case section', benchmark: { - id: 'cis_k8s', - posture_type: 'kspm', - name: 'CIS Kubernetes V1.23', - version: 'v1.0.0', + id: 'cis_gcp', + posture_type: 'cspm', + name: 'CIS Google Cloud Platform Foundation', + version: 'v2.0.0', }, type: 'process', }, - cluster_id: 'lower case cluster id', }, { '@timestamp': new Date().toISOString(), - resource: { id: chance.guid(), name: `process`, sub_type: 'Upper case type again' }, + resource: { id: cspmResourceId, name: cspmResourceName, sub_type: cspmResourceSubType }, result: { evaluation: 'failed' }, cloud: { account: { @@ -103,14 +104,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { name: 'some lower case rule name', section: 'another lower case section', benchmark: { - id: 'cis_k8s', - posture_type: 'kspm', - name: 'CIS Kubernetes V1.23', - version: 'v1.0.0', + id: 'cis_gcp', + posture_type: 'cspm', + name: 'CIS Google Cloud Platform Foundation', + version: 'v2.0.0', }, type: 'process', }, - cluster_id: 'another lower case cluster id', }, ]; @@ -143,19 +143,57 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); describe('Default Grouping', async () => { - it('groups findings by resource and sort case sensitive asc', async () => { + it('groups findings by resource and sort by compliance score desc', async () => { const groupSelector = await findings.groupSelector(); await groupSelector.openDropDown(); await groupSelector.setValue('Resource'); const grouping = await findings.findingsGrouping(); - const resourceOrder = ['Pod', 'kubelet', 'process']; + const resourceOrder = [ + { + resourceName: 'kubelet', + resourceId: data[0].resource.id, + resourceSubType: data[0].resource.sub_type, + findingsCount: '1', + complianceScore: '0%', + }, + { + resourceName: cspmResourceName, + resourceId: cspmResourceId, + resourceSubType: cspmResourceSubType, + findingsCount: '2', + complianceScore: '50%', + }, + { + resourceName: 'Pod', + resourceId: data[1].resource.id, + resourceSubType: data[1].resource.sub_type, + findingsCount: '1', + complianceScore: '100%', + }, + ]; - await asyncForEach(resourceOrder, async (resourceName, index) => { - const groupName = await grouping.getRowAtIndex(index); - expect(await groupName.getVisibleText()).to.be(resourceName); - }); + await asyncForEach( + resourceOrder, + async ( + { resourceName, resourceId, resourceSubType, findingsCount, complianceScore }, + index + ) => { + const groupRow = await grouping.getRowAtIndex(index); + expect(await groupRow.getVisibleText()).to.contain(resourceName); + expect(await groupRow.getVisibleText()).to.contain(resourceId); + expect(await groupRow.getVisibleText()).to.contain(resourceSubType); + expect( + await ( + await groupRow.findByTestSubject('cloudSecurityFindingsComplianceScore') + ).getVisibleText() + ).to.be(complianceScore); + expect( + await (await groupRow.findByTestSubject('findings_grouping_counter')).getVisibleText() + ).to.be(findingsCount); + } + ); const groupCount = await grouping.getGroupCount(); expect(groupCount).to.be('3 groups'); @@ -163,7 +201,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const unitCount = await grouping.getUnitCount(); expect(unitCount).to.be('4 findings'); }); - it('groups findings by rule name and sort case sensitive asc', async () => { + it('groups findings by rule name and sort by compliance score desc', async () => { const groupSelector = await findings.groupSelector(); await groupSelector.openDropDown(); await groupSelector.setValue('Rule name'); @@ -177,18 +215,50 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(unitCount).to.be('4 findings'); const ruleNameOrder = [ - 'Another upper case rule name', - 'Upper case rule name', - 'lower case rule name', - 'some lower case rule name', + { + ruleName: 'Upper case rule name', + findingsCount: '1', + complianceScore: '0%', + benchmarkName: data[0].rule.benchmark.name, + }, + { + ruleName: 'some lower case rule name', + findingsCount: '1', + complianceScore: '0%', + benchmarkName: data[3].rule.benchmark.name, + }, + { + ruleName: 'Another upper case rule name', + findingsCount: '1', + complianceScore: '100%', + benchmarkName: data[2].rule.benchmark.name, + }, + { + ruleName: 'lower case rule name', + findingsCount: '1', + complianceScore: '100%', + benchmarkName: data[1].rule.benchmark.name, + }, ]; - await asyncForEach(ruleNameOrder, async (resourceName, index) => { - const groupName = await grouping.getRowAtIndex(index); - expect(await groupName.getVisibleText()).to.be(resourceName); - }); + await asyncForEach( + ruleNameOrder, + async ({ ruleName, benchmarkName, findingsCount, complianceScore }, index) => { + const groupRow = await grouping.getRowAtIndex(index); + expect(await groupRow.getVisibleText()).to.contain(ruleName); + expect(await groupRow.getVisibleText()).to.contain(benchmarkName); + expect( + await ( + await groupRow.findByTestSubject('cloudSecurityFindingsComplianceScore') + ).getVisibleText() + ).to.be(complianceScore); + expect( + await (await groupRow.findByTestSubject('findings_grouping_counter')).getVisibleText() + ).to.be(findingsCount); + } + ); }); - it('groups findings by cloud account and sort case sensitive asc', async () => { + it('groups findings by cloud account and sort by compliance score desc', async () => { const groupSelector = await findings.groupSelector(); await groupSelector.setValue('Cloud account'); @@ -201,31 +271,98 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const unitCount = await grouping.getUnitCount(); expect(unitCount).to.be('4 findings'); - const cloudNameOrder = ['Account 1', 'Account 2', '—']; + const cloudNameOrder = [ + { + cloudName: 'Account 2', + findingsCount: '1', + complianceScore: '0%', + benchmarkName: data[3].rule.benchmark.name, + }, + { + cloudName: 'Account 1', + findingsCount: '1', + complianceScore: '100%', + benchmarkName: data[2].rule.benchmark.name, + }, + { + cloudName: 'No cloud account', + findingsCount: '2', + complianceScore: '50%', + benchmarkName: data[0].rule.benchmark.name, + }, + ]; - await asyncForEach(cloudNameOrder, async (resourceName, index) => { - const groupName = await grouping.getRowAtIndex(index); - expect(await groupName.getVisibleText()).to.be(resourceName); - }); + await asyncForEach( + cloudNameOrder, + async ({ cloudName, complianceScore, findingsCount, benchmarkName }, index) => { + const groupRow = await grouping.getRowAtIndex(index); + expect(await groupRow.getVisibleText()).to.contain(cloudName); + + if (cloudName !== 'No cloud account') { + expect(await groupRow.getVisibleText()).to.contain(benchmarkName); + } + + expect( + await ( + await groupRow.findByTestSubject('cloudSecurityFindingsComplianceScore') + ).getVisibleText() + ).to.be(complianceScore); + expect( + await (await groupRow.findByTestSubject('findings_grouping_counter')).getVisibleText() + ).to.be(findingsCount); + } + ); }); - it('groups findings by Kubernetes cluster and sort case sensitive asc', async () => { + it('groups findings by Kubernetes cluster and sort by compliance score desc', async () => { const groupSelector = await findings.groupSelector(); await groupSelector.setValue('Kubernetes cluster'); const grouping = await findings.findingsGrouping(); const groupCount = await grouping.getGroupCount(); - expect(groupCount).to.be('2 groups'); + expect(groupCount).to.be('3 groups'); const unitCount = await grouping.getUnitCount(); expect(unitCount).to.be('4 findings'); - const cloudNameOrder = ['Cluster 1', '—']; + const kubernetesOrder = [ + { + clusterName: 'Cluster 1', + findingsCount: '1', + complianceScore: '0%', + benchmarkName: data[0].rule.benchmark.name, + }, + { + clusterName: 'Cluster 2', + findingsCount: '1', + complianceScore: '100%', + benchmarkName: data[1].rule.benchmark.name, + }, + { + clusterName: 'No Kubernetes cluster', + findingsCount: '2', + complianceScore: '50%', + }, + ]; - await asyncForEach(cloudNameOrder, async (resourceName, index) => { - const groupName = await grouping.getRowAtIndex(index); - expect(await groupName.getVisibleText()).to.be(resourceName); - }); + await asyncForEach( + kubernetesOrder, + async ({ clusterName, complianceScore, findingsCount, benchmarkName }, index) => { + const groupRow = await grouping.getRowAtIndex(index); + expect(await groupRow.getVisibleText()).to.contain(clusterName); + if (clusterName !== 'No Kubernetes cluster') { + expect(await groupRow.getVisibleText()).to.contain(benchmarkName); + } + expect( + await ( + await groupRow.findByTestSubject('cloudSecurityFindingsComplianceScore') + ).getVisibleText() + ).to.be(complianceScore); + expect( + await (await groupRow.findByTestSubject('findings_grouping_counter')).getVisibleText() + ).to.be(findingsCount); + } + ); }); }); describe('SearchBar', () => { @@ -239,12 +376,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const grouping = await findings.findingsGrouping(); - const resourceOrder = ['kubelet']; - - await asyncForEach(resourceOrder, async (resourceName, index) => { - const groupName = await grouping.getRowAtIndex(index); - expect(await groupName.getVisibleText()).to.be(resourceName); - }); + const groupRow = await grouping.getRowAtIndex(0); + expect(await groupRow.getVisibleText()).to.contain(data[0].resource.name); const groupCount = await grouping.getGroupCount(); expect(groupCount).to.be('1 group'); @@ -272,12 +405,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const grouping = await findings.findingsGrouping(); - const resourceOrder = ['kubelet']; - - await asyncForEach(resourceOrder, async (resourceName, index) => { - const groupName = await grouping.getRowAtIndex(index); - expect(await groupName.getVisibleText()).to.be(resourceName); - }); + const groupRow = await grouping.getRowAtIndex(0); + expect(await groupRow.getVisibleText()).to.contain(data[0].resource.name); const groupCount = await grouping.getGroupCount(); expect(groupCount).to.be('1 group'); @@ -300,7 +429,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await (await firstRow.findByCssSelector('button')).click(); const latestFindingsTable = findings.createDataTableObject('latest_findings_table'); expect(await latestFindingsTable.getRowsCount()).to.be(1); - expect(await latestFindingsTable.hasColumnValue('rule.name', 'lower case rule name')).to.be( + expect(await latestFindingsTable.hasColumnValue('rule.name', data[0].rule.name)).to.be( true ); }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index 3d48d66957191..3e2e26e347dd8 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import semver from 'semver'; +import moment from 'moment'; import { AGENTS_INDEX, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { setupFleetAndAgents } from './services'; @@ -1474,13 +1475,14 @@ export default function (providerContext: FtrProviderContext) { }, }, }); + const today = new Date(Date.now()); await supertest .post(`/api/fleet/agents/bulk_upgrade`) .set('kbn-xsrf', 'xxx') .send({ version: fleetServerVersion, agents: ['agent1', 'agent2'], - start_time: new Date(Date.now()).toISOString(), + start_time: today.toISOString(), }) .expect(200); @@ -1498,10 +1500,8 @@ export default function (providerContext: FtrProviderContext) { 'minimum_execution_duration', 'expiration' ); - // calculate 1 month from now - const today = new Date(); - const nextMonthUnixTime = today.setMonth(today.getMonth() + 1); - const nextMonth = new Date(nextMonthUnixTime).toISOString().slice(0, 10); + // add 30 days from now + const nextMonth = moment(today).add(30, 'days').toISOString().slice(0, 10); expect(action.expiration).contain(`${nextMonth}`); expect(action.agents).contain('agent1'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/alerts_compatibility.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/alerts_compatibility.ts index 7df54659da8ce..1194a1f8df867 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/alerts_compatibility.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/alerts_compatibility.ts @@ -320,6 +320,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.status': 'active', 'kibana.alert.workflow_status': 'open', 'kibana.alert.workflow_tags': [], + 'kibana.alert.workflow_assignee_ids': [], 'kibana.alert.depth': 2, 'kibana.alert.reason': 'event on security-linux-1 created high alert Alert Testing Query.', @@ -482,6 +483,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.status': 'active', 'kibana.alert.workflow_status': 'open', 'kibana.alert.workflow_tags': [], + 'kibana.alert.workflow_assignee_ids': [], 'kibana.alert.depth': 2, 'kibana.alert.reason': 'event on security-linux-1 created high alert Alert Testing Query.', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments.ts new file mode 100644 index 0000000000000..b520b505e0405 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments.ts @@ -0,0 +1,519 @@ +/* + * 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 expect from '@kbn/expect'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { + DETECTION_ENGINE_ALERT_ASSIGNEES_URL, + DETECTION_ENGINE_QUERY_SIGNALS_URL, +} from '@kbn/security-solution-plugin/common/constants'; +import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { + createAlertsIndex, + createRule, + deleteAllAlerts, + deleteAllRules, + getAlertsByIds, + getQueryAlertIds, + getRuleForAlertTesting, + setAlertAssignees, + waitForAlertsToBePresent, + waitForRuleSuccess, +} from '../../../utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@ess @serverless Alert User Assignment - ESS & Serverless', () => { + describe('validation checks', () => { + it('should give errors when no alert ids are provided', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send(setAlertAssignees({ assigneesToAdd: [], assigneesToRemove: [], ids: [] })) + .expect(400); + + expect(body).to.eql({ + error: 'Bad Request', + message: '[request body]: ids: Array must contain at least 1 element(s)', + statusCode: 400, + }); + }); + + it('should give errors when empty alert ids are provided', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send(setAlertAssignees({ assigneesToAdd: [], assigneesToRemove: [], ids: ['123', ''] })) + .expect(400); + + expect(body).to.eql({ + error: 'Bad Request', + message: + '[request body]: ids.1: String must contain at least 1 character(s), ids.1: Invalid', + statusCode: 400, + }); + }); + + it('should give errors when duplicate assignees exist in both add and remove', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['test-1'], + assigneesToRemove: ['test-1'], + ids: ['123'], + }) + ) + .expect(400); + + expect(body).to.eql({ + message: ['Duplicate assignees ["test-1"] were found in the add and remove parameters.'], + status_code: 400, + }); + }); + }); + + describe('tests with auditbeat data', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + + describe('updating assignees', () => { + it('should add new assignees to single alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + const alertId = alertIds[0]; + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1'], + assigneesToRemove: [], + ids: [alertId], + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds([alertId])) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql(['user-1']); + }); + }); + + it('should add new assignees to multiple alerts', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-2', 'user-3'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-2', + 'user-3', + ]); + }); + }); + + it('should update assignees for single alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + const alertId = alertIds[0]; + + // Assign users + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: [alertId], + }) + ) + .expect(200); + + // Update assignees + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-3'], + assigneesToRemove: ['user-2'], + ids: [alertId], + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds([alertId])) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-3', + ]); + }); + }); + + it('should update assignees for multiple alerts', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + // Assign users + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + // Update assignees + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-3'], + assigneesToRemove: ['user-2'], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-3', + ]); + }); + }); + + it('should add assignee once to the alert even if same assignee was passed multiple times', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-1', 'user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + + it('should remove assignee once to the alert even if same assignee was passed multiple times', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: [], + assigneesToRemove: ['user-2', 'user-2'], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql(['user-1']); + }); + }); + + it('should not update assignees if both `add` and `remove` are empty', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: [], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + + it('should not update assignees when adding user which is assigned to alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + + it('should not update assignees when removing user which is not assigned to alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: [], + assigneesToRemove: ['user-3'], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts new file mode 100644 index 0000000000000..527d02295f6a0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts @@ -0,0 +1,92 @@ +/* + * 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 { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { ROLES } from '@kbn/security-solution-plugin/common/test'; + +import { + createUserAndRole, + deleteUserAndRole, +} from '../../../../../../common/services/security_solution'; +import { + createAlertsIndex, + createRule, + deleteAllAlerts, + deleteAllRules, + getAlertsByIds, + getRuleForAlertTesting, + setAlertAssignees, + waitForAlertsToBePresent, + waitForRuleSuccess, +} from '../../../utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@ess Alert User Assignment - ESS', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + + describe('authorization / RBAC', () => { + it('should not allow viewer user to assign alerts', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + const userAndRole = ROLES.reader; + await createUserAndRole(getService, userAndRole); + + // Try to set all of the alerts to the state of closed. + // This should not be possible with the given user. + await supertestWithoutAuth + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .auth(userAndRole, 'changeme') // each user has the same password + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(403); + + await deleteUserAndRole(getService, userAndRole); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts new file mode 100644 index 0000000000000..dd41574c56940 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts @@ -0,0 +1,111 @@ +/* + * 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 { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { ROLES } from '@kbn/security-solution-plugin/common/test'; + +import { + createAlertsIndex, + createRule, + deleteAllAlerts, + deleteAllRules, + getAlertsByIds, + getRuleForAlertTesting, + setAlertAssignees, + waitForAlertsToBePresent, + waitForRuleSuccess, +} from '../../../utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@serverless Alert User Assignment - Serverless', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + + describe('authorization / RBAC', () => { + const successfulAssignWithRole = async (userAndRole: ROLES) => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + // Try to set all of the alerts to the state of closed. + // This should not be possible with the given user. + await supertestWithoutAuth + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .auth(userAndRole, 'changeme') // each user has the same password + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + }; + + it('should allow `ROLES.t1_analyst` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.t1_analyst); + }); + + it('should allow `ROLES.t2_analyst` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.t2_analyst); + }); + + it('should allow `ROLES.t3_analyst` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.t3_analyst); + }); + + it('should allow `ROLES.detections_admin` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.detections_admin); + }); + + it('should allow `ROLES.platform_engineer` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.platform_engineer); + }); + + it('should allow `ROLES.rule_author` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.rule_author); + }); + + it('should allow `ROLES.soc_manager` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.soc_manager); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts new file mode 100644 index 0000000000000..401f92ea9dcf6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Alert assignments API', function () { + loadTestFile(require.resolve('./assignments')); + loadTestFile(require.resolve('./assignments_ess')); + loadTestFile(require.resolve('./assignments_serverless')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts index 7482e1bac558f..85e2e602a8929 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./migrations')); loadTestFile(require.resolve('./open_close_alerts')); loadTestFile(require.resolve('./set_alert_tags')); + loadTestFile(require.resolve('./assignments')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts index 9cb85e3366cee..db5a924b48a05 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts @@ -11,6 +11,7 @@ import { ALERT_RULE_UUID, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, + ALERT_WORKFLOW_ASSIGNEE_IDS, EVENT_KIND, } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; @@ -155,6 +156,7 @@ export default ({ getService }: FtrProviderContext) => { [ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME], [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], [ALERT_DEPTH]: 1, [ALERT_ANCESTORS]: [ { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/esql.ts index 7521ff18bc0a1..2ae1c9e1101fd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/esql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/esql.ts @@ -151,6 +151,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.rule.updated_by': 'elastic', 'kibana.alert.rule.version': 1, 'kibana.alert.workflow_tags': [], + 'kibana.alert.workflow_assignee_ids': [], 'kibana.alert.rule.risk_score': 55, 'kibana.alert.rule.severity': 'high', }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts index e4d4a596eee3b..b569b443013de 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts @@ -15,6 +15,7 @@ import { ALERT_UUID, ALERT_WORKFLOW_STATUS, ALERT_WORKFLOW_TAGS, + ALERT_WORKFLOW_ASSIGNEE_IDS, SPACE_IDS, VERSION, } from '@kbn/rule-data-utils'; @@ -125,6 +126,7 @@ export default ({ getService }: FtrProviderContext) => { [ALERT_ANCESTORS]: expect.any(Array), [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], [ALERT_STATUS]: 'active', [SPACE_IDS]: ['default'], [ALERT_SEVERITY]: 'critical', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts index aa6d51048bbce..0d7572144fcea 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts @@ -166,6 +166,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.status': 'active', 'kibana.alert.workflow_status': 'open', 'kibana.alert.workflow_tags': [], + 'kibana.alert.workflow_assignee_ids': [], 'kibana.alert.depth': 1, 'kibana.alert.reason': 'authentication event with source 8.42.77.171 by root on zeek-newyork-sha-aa8df15 created high alert Query with a rule id.', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts index 8d7cf8fd9f89b..9b6c525b5e351 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts @@ -18,6 +18,7 @@ import { SPACE_IDS, VERSION, ALERT_WORKFLOW_TAGS, + ALERT_WORKFLOW_ASSIGNEE_IDS, } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; @@ -294,6 +295,7 @@ export default ({ getService }: FtrProviderContext) => { [ALERT_UUID]: fullAlert[ALERT_UUID], [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_WORKFLOW_TAGS]: [], + [ALERT_WORKFLOW_ASSIGNEE_IDS]: [], [SPACE_IDS]: ['default'], [VERSION]: fullAlert[VERSION], threat: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts new file mode 100644 index 0000000000000..59c70d5d6bd9e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts @@ -0,0 +1,25 @@ +/* + * 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 { AlertIds } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { SetAlertAssigneesRequestBody } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +export const setAlertAssignees = ({ + assigneesToAdd, + assigneesToRemove, + ids, +}: { + assigneesToAdd: string[]; + assigneesToRemove: string[]; + ids: AlertIds; +}): SetAlertAssigneesRequestBody => ({ + assignees: { + add: assigneesToAdd, + remove: assigneesToRemove, + }, + ids, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts index e78bfa1922d36..867f85653ef4f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts @@ -21,4 +21,5 @@ export * from './get_query_alert_ids'; export * from './set_alert_tags'; export * from './get_preview_alerts'; export * from './get_alert_status'; +export * from './alert_assignees'; export * from './migrations'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/asset_criticality.ts index 8953d9986c035..de5f64aaaa3a1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/asset_criticality.ts @@ -10,6 +10,8 @@ import { cleanRiskEngine, cleanAssetCriticality, assetCriticalityRouteHelpersFactory, + getAssetCriticalityDoc, + getAssetCriticalityIndex, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -33,13 +35,11 @@ export default ({ getService }: FtrProviderContext) => { describe('initialisation of resources', () => { it('should has index installed on status api call', async () => { - const assetCriticalityIndex = '.asset-criticality.asset-criticality-default'; - let assetCriticalityIndexExist; try { assetCriticalityIndexExist = await es.indices.exists({ - index: assetCriticalityIndex, + index: getAssetCriticalityIndex(), }); } catch (e) { assetCriticalityIndexExist = false; @@ -54,7 +54,7 @@ export default ({ getService }: FtrProviderContext) => { }); const assetCriticalityIndexResult = await es.indices.get({ - index: assetCriticalityIndex, + index: getAssetCriticalityIndex(), }); expect( @@ -81,5 +81,125 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('create', () => { + it('should correctly create asset criticality', async () => { + const assetCriticality = { + id_field: 'host.name', + id_value: 'host-01', + criticality_level: 'important', + }; + + const { body: result } = await assetCriticalityRoutes.upsert(assetCriticality); + + expect(result.id_field).to.eql('host.name'); + expect(result.id_value).to.eql('host-01'); + expect(result.criticality_level).to.eql('important'); + expect(result['@timestamp']).to.be.a('string'); + + const doc = await getAssetCriticalityDoc({ idField: 'host.name', idValue: 'host-01', es }); + + expect(doc).to.eql(result); + }); + + it('should return 400 if criticality is invalid', async () => { + const assetCriticality = { + id_field: 'host.name', + id_value: 'host-01', + criticality_level: 'invalid', + }; + + await assetCriticalityRoutes.upsert(assetCriticality, { + expectStatusCode: 400, + }); + }); + + it('should return 400 if id_field is invalid', async () => { + const assetCriticality = { + id_field: 'invalid', + id_value: 'host-01', + criticality_level: 'important', + }; + + await assetCriticalityRoutes.upsert(assetCriticality, { + expectStatusCode: 400, + }); + }); + }); + + describe('read', () => { + it('should correctly get asset criticality', async () => { + const assetCriticality = { + id_field: 'host.name', + id_value: 'host-02', + criticality_level: 'important', + }; + + await assetCriticalityRoutes.upsert(assetCriticality); + + const { body: result } = await assetCriticalityRoutes.get('host.name', 'host-02'); + + expect(result.id_field).to.eql('host.name'); + expect(result.id_value).to.eql('host-02'); + expect(result.criticality_level).to.eql('important'); + expect(result['@timestamp']).to.be.a('string'); + }); + + it('should return a 400 if id_field is invalid', async () => { + await assetCriticalityRoutes.get('invalid', 'host-02', { + expectStatusCode: 400, + }); + }); + }); + + describe('update', () => { + it('should correctly update asset criticality', async () => { + const assetCriticality = { + id_field: 'host.name', + id_value: 'host-01', + criticality_level: 'important', + }; + + const { body: createdDoc } = await assetCriticalityRoutes.upsert(assetCriticality); + const updatedAssetCriticality = { + id_field: 'host.name', + id_value: 'host-01', + criticality_level: 'very_important', + }; + + const { body: updatedDoc } = await assetCriticalityRoutes.upsert(updatedAssetCriticality); + + expect(updatedDoc.id_field).to.eql('host.name'); + expect(updatedDoc.id_value).to.eql('host-01'); + expect(updatedDoc.criticality_level).to.eql('very_important'); + expect(updatedDoc['@timestamp']).to.be.a('string'); + expect(updatedDoc['@timestamp']).to.not.eql(createdDoc['@timestamp']); + + const doc = await getAssetCriticalityDoc({ idField: 'host.name', idValue: 'host-01', es }); + + expect(doc).to.eql(updatedDoc); + }); + }); + + describe('delete', () => { + it('should correctly delete asset criticality', async () => { + const assetCriticality = { + id_field: 'host.name', + id_value: 'delete-me', + criticality_level: 'important', + }; + + await assetCriticalityRoutes.upsert(assetCriticality); + + await assetCriticalityRoutes.delete('host.name', 'delete-me'); + const doc = await getAssetCriticalityDoc({ + idField: 'host.name', + idValue: 'delete-me', + es, + }); + + expect(doc).to.eql(undefined); + }); + }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts index bb4e5bc80dd96..97686465c8073 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts @@ -19,7 +19,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.get('kbnTestServer.serverArgs'), `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'entityAnalyticsAssetCriticalityEnabled', - 'riskEnginePrivilegesRouteEnabled', ])}`, ], }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts index b9add84ac202b..ccbbcd9dc8cb8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts @@ -11,7 +11,6 @@ export default createTestConfig({ kbnTestServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'entityAnalyticsAssetCriticalityEnabled', - 'riskEnginePrivilegesRouteEnabled', ])}`, ], testFiles: [require.resolve('..')], diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts index 2bc7de4a895f5..9318bc7d77991 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts @@ -10,11 +10,18 @@ import { ELASTIC_HTTP_VERSION_HEADER, X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; -import { ASSET_CRITICALITY_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + ASSET_CRITICALITY_STATUS_URL, + ASSET_CRITICALITY_URL, +} from '@kbn/security-solution-plugin/common/constants'; import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; +import querystring from 'querystring'; import { routeWithNamespace } from '../../detections_response/utils'; +export const getAssetCriticalityIndex = (namespace?: string) => + `.asset-criticality.asset-criticality-${namespace ?? 'default'}`; + export const cleanAssetCriticality = async ({ log, es, @@ -27,7 +34,7 @@ export const cleanAssetCriticality = async ({ try { await Promise.allSettled([ es.indices.delete({ - index: [`.asset-criticality.asset-criticality-${namespace}`], + index: [getAssetCriticalityIndex(namespace)], }), ]); } catch (e) { @@ -35,6 +42,24 @@ export const cleanAssetCriticality = async ({ } }; +export const getAssetCriticalityDoc = async (opts: { + es: Client; + idField: string; + idValue: string; +}) => { + const { es, idField, idValue } = opts; + try { + const doc = await es.get({ + index: getAssetCriticalityIndex(), + id: `${idField}:${idValue}`, + }); + + return doc._source; + } catch (e) { + return undefined; + } +}; + export const assetCriticalityRouteHelpersFactory = ( supertest: SuperTest.SuperTest, namespace?: string @@ -47,4 +72,39 @@ export const assetCriticalityRouteHelpersFactory = ( .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200), + upsert: async ( + body: Record, + { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } + ) => + await supertest + .post(routeWithNamespace(ASSET_CRITICALITY_URL, namespace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(body) + .expect(expectStatusCode), + delete: async (idField: string, idValue: string) => { + const qs = querystring.stringify({ id_field: idField, id_value: idValue }); + const route = `${routeWithNamespace(ASSET_CRITICALITY_URL, namespace)}?${qs}`; + return supertest + .delete(route) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200); + }, + get: async ( + idField: string, + idValue: string, + { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } + ) => { + const qs = querystring.stringify({ id_field: idField, id_value: idValue }); + const route = `${routeWithNamespace(ASSET_CRITICALITY_URL, namespace)}?${qs}`; + return supertest + .get(route) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(expectStatusCode); + }, }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts new file mode 100644 index 0000000000000..83595c1f81e90 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts @@ -0,0 +1,368 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { + closeAlertFlyout, + closeAlerts, + expandFirstAlert, + selectFirstPageAlerts, + selectNumberOfAlerts, + selectPageFilterValue, +} from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertDetailsFlyoutShowsAssignees, + alertDetailsFlyoutShowsAssigneesBadge, + alertsTableShowsAssigneesBadgeForAlert, + alertsTableShowsAssigneesForAlert, + updateAssigneesForAlert, + checkEmptyAssigneesStateInAlertDetailsFlyout, + checkEmptyAssigneesStateInAlertsTable, + removeAllAssigneesForAlert, + bulkUpdateAssignees, + alertsTableShowsAssigneesForAllAlerts, + bulkRemoveAllAssignees, + filterByAssignees, + NO_ASSIGNEES, + clearAssigneesFilter, + updateAssigneesViaAddButtonInFlyout, + updateAssigneesViaTakeActionButtonInFlyout, + removeAllAssigneesViaTakeActionButtonInFlyout, + loadPageAs, +} from '../../../../tasks/alert_assignments'; +import { ALERTS_COUNT } from '../../../../screens/alerts'; + +describe('Alert user assignment - ESS & Serverless', { tags: ['@ess', '@serverless'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + + // Login into accounts so that they got activated and visible in user profiles list + login(ROLES.t1_analyst); + login(ROLES.t2_analyst); + login(ROLES.t3_analyst); + login(ROLES.soc_manager); + login(ROLES.detections_admin); + login(ROLES.platform_engineer); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + context('Basic rendering', () => { + it('alert with no assignees in alerts table', () => { + checkEmptyAssigneesStateInAlertsTable(); + }); + + it(`alert with no assignees in alert's details flyout`, () => { + expandFirstAlert(); + checkEmptyAssigneesStateInAlertDetailsFlyout(); + }); + + it('alert with some assignees in alerts table', () => { + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(users); + alertsTableShowsAssigneesForAlert(users); + }); + + it(`alert with some assignees in alert's details flyout`, () => { + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(users); + expandFirstAlert(); + alertDetailsFlyoutShowsAssignees(users); + }); + + it('alert with many assignees (collapsed into badge) in alerts table', () => { + const users = [ + ROLES.t1_analyst, + ROLES.t2_analyst, + ROLES.t3_analyst, + ROLES.soc_manager, + ROLES.detections_admin, + ]; + updateAssigneesForAlert(users); + alertsTableShowsAssigneesBadgeForAlert(users); + }); + + it(`alert with many assignees (collapsed into badge) in alert's details flyout`, () => { + const users = [ROLES.detections_admin, ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesForAlert(users); + expandFirstAlert(); + alertDetailsFlyoutShowsAssigneesBadge(users); + }); + }); + + context('Updating assignees (single alert)', () => { + it('adding new assignees via `More actions` in alerts table', () => { + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(users); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert(users); + + // Assignees should appear in the alert's details flyout + expandFirstAlert(); + alertDetailsFlyoutShowsAssignees(users); + }); + + it('adding new assignees via add button in flyout', () => { + expandFirstAlert(); + + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaAddButtonInFlyout(users); + + // Assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(users); + + // Assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(users); + }); + + it('adding new assignees via `Take action` button in flyout', () => { + expandFirstAlert(); + + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(users); + + // Assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(users); + + // Assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(users); + }); + + it('updating assignees via `More actions` in alerts table', () => { + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(initialAssignees); + alertsTableShowsAssigneesForAlert(initialAssignees); + + // Update assignees + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesForAlert(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert(expectedAssignees); + + // Expected assignees should appear in the alert's details flyout + expandFirstAlert(); + alertDetailsFlyoutShowsAssignees(expectedAssignees); + }); + + it('updating assignees via add button in flyout', () => { + expandFirstAlert(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaAddButtonInFlyout(initialAssignees); + alertDetailsFlyoutShowsAssignees(initialAssignees); + + // Update assignees + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesViaAddButtonInFlyout(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(expectedAssignees); + + // Expected assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(expectedAssignees); + }); + + it('updating assignees via `Take action` button in flyout', () => { + expandFirstAlert(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(initialAssignees); + alertDetailsFlyoutShowsAssignees(initialAssignees); + + // Update assignees + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(expectedAssignees); + + // Expected assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(expectedAssignees); + }); + + it('removing all assignees via `More actions` in alerts table', () => { + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(initialAssignees); + alertsTableShowsAssigneesForAlert(initialAssignees); + + removeAllAssigneesForAlert(); + + // Alert should not show any assignee in alerts table or in details flyout + checkEmptyAssigneesStateInAlertsTable(); + expandFirstAlert(); + checkEmptyAssigneesStateInAlertDetailsFlyout(); + }); + + it('removing all assignees via `Take action` button in flyout', () => { + expandFirstAlert(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(initialAssignees); + alertDetailsFlyoutShowsAssignees(initialAssignees); + + removeAllAssigneesViaTakeActionButtonInFlyout(); + + // Alert should not show any assignee in alerts table or in details flyout + checkEmptyAssigneesStateInAlertDetailsFlyout(); + closeAlertFlyout(); + checkEmptyAssigneesStateInAlertsTable(); + }); + }); + + context('Updating assignees (bulk actions)', () => { + it('adding new assignees should be reflected in UI (alerts table and details flyout)', () => { + selectFirstPageAlerts(); + + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + bulkUpdateAssignees(users); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAllAlerts(users); + }); + + it('updating assignees should be reflected in UI (alerts table and details flyout)', () => { + selectFirstPageAlerts(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + bulkUpdateAssignees(initialAssignees); + alertsTableShowsAssigneesForAllAlerts(initialAssignees); + + // Update assignees + selectFirstPageAlerts(); + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + bulkUpdateAssignees(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alerts table + alertsTableShowsAssigneesForAllAlerts(expectedAssignees); + }); + + it('removing all assignees should be reflected in UI (alerts table and details flyout)', () => { + selectFirstPageAlerts(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + bulkUpdateAssignees(initialAssignees); + alertsTableShowsAssigneesForAllAlerts(initialAssignees); + + // Unassign alert + selectFirstPageAlerts(); + bulkRemoveAllAssignees(); + + // Alerts should not have assignees + checkEmptyAssigneesStateInAlertsTable(); + }); + }); + + context('Alerts filtering', () => { + it('by `No assignees` option', () => { + const totalNumberOfAlerts = 5; + const numberOfSelectedAlerts = 2; + selectNumberOfAlerts(numberOfSelectedAlerts); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([NO_ASSIGNEES]); + + const expectedNumberOfAlerts = totalNumberOfAlerts - numberOfSelectedAlerts; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAlerts); + }); + + it('by one assignee', () => { + const numberOfSelectedAlerts = 2; + selectNumberOfAlerts(numberOfSelectedAlerts); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([ROLES.t1_analyst]); + + cy.get(ALERTS_COUNT).contains(numberOfSelectedAlerts); + }); + + it('by multiple assignees', () => { + const numberOfSelectedAlerts1 = 1; + selectNumberOfAlerts(numberOfSelectedAlerts1); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([NO_ASSIGNEES]); + + const numberOfSelectedAlerts2 = 2; + selectNumberOfAlerts(numberOfSelectedAlerts2); + bulkUpdateAssignees([ROLES.detections_admin]); + + clearAssigneesFilter(); + + cy.get(ALERTS_COUNT).contains(5); + + filterByAssignees([ROLES.t1_analyst, ROLES.detections_admin]); + + const expectedNumberOfAlerts = numberOfSelectedAlerts1 + numberOfSelectedAlerts2; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAlerts); + }); + + it('by assignee and alert status', () => { + const totalNumberOfAlerts = 5; + const numberOfAssignedAlerts = 3; + selectNumberOfAlerts(numberOfAssignedAlerts); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([ROLES.t1_analyst]); + + const numberOfClosedAlerts = 1; + selectNumberOfAlerts(numberOfClosedAlerts); + closeAlerts(); + + const expectedNumberOfAllerts1 = numberOfAssignedAlerts - numberOfClosedAlerts; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAllerts1); + + clearAssigneesFilter(); + + const expectedNumberOfAllerts2 = totalNumberOfAlerts - numberOfClosedAlerts; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAllerts2); + + filterByAssignees([ROLES.t1_analyst]); + selectPageFilterValue(0, 'closed'); + cy.get(ALERTS_COUNT).contains(numberOfClosedAlerts); + }); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts new file mode 100644 index 0000000000000..bdaaedab7f0bf --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts @@ -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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { expandFirstAlert } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertsTableMoreActionsAreNotAvailable, + cannotAddAssigneesViaDetailsFlyout, + loadPageAs, +} from '../../../../tasks/alert_assignments'; + +describe('Alert user assignment - ESS', { tags: ['@ess'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + it('viewer/reader should not be able to update assignees', () => { + // Login as a reader + loadPageAs(ALERTS_URL, ROLES.reader); + waitForAlertsToPopulate(); + + // Check alerts table + alertsTableMoreActionsAreNotAvailable(); + + // Check alert's details flyout + expandFirstAlert(); + cannotAddAssigneesViaDetailsFlyout(); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts new file mode 100644 index 0000000000000..34bab70e43b0f --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts @@ -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 { login } from '../../../../tasks/login'; +import { getNewRule } from '../../../../objects/rule'; +import { expandFirstAlert } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + asigneesMenuItemsAreNotAvailable, + cannotAddAssigneesViaDetailsFlyout, + loadPageAs, +} from '../../../../tasks/alert_assignments'; + +describe('Alert user assignment - Basic License', { tags: ['@ess'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + login(); + cy.request({ + method: 'POST', + url: '/api/license/start_basic?acknowledge=true', + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + }, + }).then(({ body }) => { + cy.log(`body: ${JSON.stringify(body)}`); + expect(body).contains({ + acknowledged: true, + basic_was_started: true, + }); + }); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + it('user with Basic license should not be able to update assignees', () => { + // Check alerts table + asigneesMenuItemsAreNotAvailable(); + + // Check alert's details flyout + expandFirstAlert(); + cannotAddAssigneesViaDetailsFlyout(); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts new file mode 100644 index 0000000000000..ff9f3801644a2 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts @@ -0,0 +1,88 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { refreshAlertPageFilter, selectFirstPageAlerts } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertsTableShowsAssigneesForAlert, + updateAssigneesForAlert, + bulkRemoveAllAssignees, + loadPageAs, +} from '../../../../tasks/alert_assignments'; + +describe( + 'Alert user assignment - Serverless Complete', + { + tags: ['@serverless'], + env: { + ftrConfig: { + productTypes: [ + { product_line: 'security', product_tier: 'complete' }, + { product_line: 'endpoint', product_tier: 'complete' }, + ], + }, + }, + }, + () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + + // Login into accounts so that they got activated and visible in user profiles list + login(ROLES.t1_analyst); + login(ROLES.t2_analyst); + login(ROLES.t3_analyst); + login(ROLES.soc_manager); + login(ROLES.detections_admin); + login(ROLES.platform_engineer); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + context('Authorization / RBAC', () => { + it('users with editing privileges should be able to update assignees', () => { + const editors = [ + ROLES.t1_analyst, + ROLES.t2_analyst, + ROLES.t3_analyst, + ROLES.rule_author, + ROLES.soc_manager, + ROLES.detections_admin, + ROLES.platform_engineer, + ]; + editors.forEach((role) => { + loadPageAs(ALERTS_URL, role); + waitForAlertsToPopulate(); + + // Unassign alert + selectFirstPageAlerts(); + bulkRemoveAllAssignees(); + refreshAlertPageFilter(); + + updateAssigneesForAlert([role]); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert([role]); + }); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts new file mode 100644 index 0000000000000..53436e0102f0a --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts @@ -0,0 +1,88 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { refreshAlertPageFilter, selectFirstPageAlerts } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertsTableShowsAssigneesForAlert, + updateAssigneesForAlert, + bulkRemoveAllAssignees, + loadPageAs, +} from '../../../../tasks/alert_assignments'; + +describe( + 'Alert user assignment - Serverless Essentials', + { + tags: ['@serverless'], + env: { + ftrConfig: { + productTypes: [ + { product_line: 'security', product_tier: 'essentials' }, + { product_line: 'endpoint', product_tier: 'essentials' }, + ], + }, + }, + }, + () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + + // Login into accounts so that they got activated and visible in user profiles list + login(ROLES.t1_analyst); + login(ROLES.t2_analyst); + login(ROLES.t3_analyst); + login(ROLES.soc_manager); + login(ROLES.detections_admin); + login(ROLES.platform_engineer); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + context('Authorization / RBAC', () => { + it('users with editing privileges should be able to update assignees', () => { + const editors = [ + ROLES.t1_analyst, + ROLES.t2_analyst, + ROLES.t3_analyst, + ROLES.rule_author, + ROLES.soc_manager, + ROLES.detections_admin, + ROLES.platform_engineer, + ]; + editors.forEach((role) => { + loadPageAs(ALERTS_URL, role); + waitForAlertsToPopulate(); + + // Unassign alert + selectFirstPageAlerts(); + bulkRemoveAllAssignees(); + refreshAlertPageFilter(); + + updateAssigneesForAlert([role]); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert([role]); + }); + }); + }); + } +); 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 f3a898e430a4e..0e10557bcaf0e 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 @@ -38,8 +38,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { const rule = getEsqlRule(); const expectedNumberOfRules = 1; - // FLAKY: https://github.com/elastic/kibana/issues/172251 - describe.skip('creation', () => { + describe('creation', () => { beforeEach(() => { deleteAlertsAndRules(); login(); 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 445b33a682f9c..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 @@ -34,10 +34,7 @@ const rule = getEsqlRule(); const expectedValidEsqlQuery = 'from auditbeat* | stats count(event.category) by event.category'; -// FLAKY: https://github.com/elastic/kibana/issues/172253 -// FLAKY: https://github.com/elastic/kibana/issues/172254 -// FLAKY: https://github.com/elastic/kibana/issues/172255 -describe.skip('Detection ES|QL rules, edit', { tags: ['@ess'] }, () => { +describe('Detection ES|QL rules, edit', { tags: ['@ess'] }, () => { beforeEach(() => { login(); deleteAlertsAndRules(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page_privileges_callout.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page_privileges_callout.ts index a00a5a9ee92cb..0072b653a3e7b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page_privileges_callout.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page_privileges_callout.ts @@ -26,9 +26,6 @@ describe( 'Entity analytics management page - Risk Engine Privileges Callout', { tags: ['@ess'], - env: { - ftrConfig: { enableExperimental: ['riskEnginePrivilegesRouteEnabled'] }, - }, }, () => { it('should not show the callout for superuser', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 0eaec7ce0b471..cfcd52372daeb 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -33,6 +33,8 @@ export const ALERT_SEVERITY = '[data-test-subj="formatted-field-kibana.alert.sev export const ALERT_DATA_GRID = '[data-test-subj="euiDataGridBody"]'; +export const ALERT_DATA_GRID_ROW = `${ALERT_DATA_GRID} .euiDataGridRow`; + export const ALERTS_COUNT = '[data-test-subj="toolbar-alerts-count"]'; export const CLOSE_ALERT_BTN = '[data-test-subj="close-alert-status"]'; @@ -180,3 +182,36 @@ export const ALERT_RENDERER_HOST_NAME = '[data-test-subj="alertFieldBadge"] [data-test-subj="render-content-host.name"]'; export const HOVER_ACTIONS_CONTAINER = getDataTestSubjectSelector('hover-actions-container'); + +export const ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM = '.euiSelectableListItem'; +export const ALERT_USERS_PROFILES_CLEAR_SEARCH_BUTTON = '[data-test-subj="clearSearchButton"]'; + +export const ALERT_ASSIGN_CONTEXT_MENU_ITEM = + '[data-test-subj="alert-assignees-context-menu-item"]'; + +export const ALERT_UNASSIGN_CONTEXT_MENU_ITEM = + '[data-test-subj="remove-alert-assignees-menu-item"]'; + +export const ALERT_ASSIGNEES_SELECT_PANEL = + '[data-test-subj="securitySolutionAssigneesApplyPanel"]'; + +export const ALERT_ASSIGNEES_UPDATE_BUTTON = + '[data-test-subj="securitySolutionAssigneesApplyButton"]'; + +export const ALERT_USER_AVATAR = (assignee: string) => + `[data-test-subj="securitySolutionUsersAvatar-${assignee}"][title='${assignee}']`; + +export const ALERT_AVATARS_PANEL = '[data-test-subj="securitySolutionUsersAvatarsPanel"]'; + +export const ALERT_ASIGNEES_COLUMN = + '[data-test-subj="dataGridRowCell"][data-gridcell-column-id="kibana.alert.workflow_assignee_ids"]'; + +export const ALERT_ASSIGNEES_COUNT_BADGE = + '[data-test-subj="securitySolutionUsersAvatarsCountBadge"]'; + +export const FILTER_BY_ASSIGNEES_BUTTON = '[data-test-subj="filter-popover-button-assignees"]'; + +export const ALERT_DETAILS_ASSIGN_BUTTON = + '[data-test-subj="securitySolutionFlyoutHeaderAssigneesAddButton"]'; + +export const ALERT_DETAILS_TAKE_ACTION_BUTTON = '[data-test-subj="take-action-dropdown-btn"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts index abf9585e368ec..afe87189acd37 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts @@ -17,6 +17,7 @@ import { SEVERITY_VALUE_TEST_ID, STATUS_BUTTON_TEST_ID, FLYOUT_HEADER_TITLE_TEST_ID, + ASSIGNEES_HEADER_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/document_details/right/components/test_ids'; import { COLLAPSE_DETAILS_BUTTON_TEST_ID, @@ -59,6 +60,8 @@ export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE = getDataTestSubjectSelector(RISK_SCORE_VALUE_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE = getDataTestSubjectSelector(SEVERITY_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES = + getDataTestSubjectSelector(ASSIGNEES_HEADER_TEST_ID); /* Footer */ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts new file mode 100644 index 0000000000000..c0adfbf1d78fa --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts @@ -0,0 +1,236 @@ +/* + * 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 { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; +import { + ALERTS_TABLE_ROW_LOADER, + ALERT_AVATARS_PANEL, + ALERT_ASSIGNEES_SELECT_PANEL, + ALERT_ASSIGN_CONTEXT_MENU_ITEM, + ALERT_ASSIGNEES_UPDATE_BUTTON, + ALERT_USER_AVATAR, + ALERT_DATA_GRID_ROW, + ALERT_DETAILS_ASSIGN_BUTTON, + ALERT_DETAILS_TAKE_ACTION_BUTTON, + ALERT_UNASSIGN_CONTEXT_MENU_ITEM, + ALERT_USERS_PROFILES_CLEAR_SEARCH_BUTTON, + ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM, + ALERT_ASIGNEES_COLUMN, + ALERT_ASSIGNEES_COUNT_BADGE, + FILTER_BY_ASSIGNEES_BUTTON, + TAKE_ACTION_POPOVER_BTN, + TIMELINE_CONTEXT_MENU_BTN, +} from '../screens/alerts'; +import { PAGE_TITLE } from '../screens/common/page'; +import { DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES } from '../screens/expandable_flyout/alert_details_right_panel'; +import { selectFirstPageAlerts } from './alerts'; +import { login } from './login'; +import { visitWithTimeRange } from './navigation'; + +export const NO_ASSIGNEES = 'No assignees'; + +export const waitForAssigneesToPopulatePopover = () => { + cy.waitUntil( + () => { + cy.log('Waiting for assignees to appear in popover'); + return cy.root().then(($el) => { + const $updateButton = $el.find(ALERT_ASSIGNEES_UPDATE_BUTTON); + return !$updateButton.prop('disabled'); + }); + }, + { interval: 500, timeout: 12000 } + ); +}; + +export const waitForPageTitleToBeShown = () => { + cy.get(PAGE_TITLE).should('be.visible'); +}; + +export const loadPageAs = (url: string, role?: SecurityRoleName) => { + login(role); + visitWithTimeRange(url); + waitForPageTitleToBeShown(); +}; + +export const openAlertAssigningActionMenu = (alertIndex = 0) => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(alertIndex).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).click(); +}; + +export const openAlertAssigningBulkActionMenu = () => { + cy.get(TAKE_ACTION_POPOVER_BTN).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).click(); +}; + +export const updateAlertAssignees = () => { + cy.get(ALERT_ASSIGNEES_UPDATE_BUTTON).click(); +}; + +export const checkEmptyAssigneesStateInAlertsTable = () => { + cy.get(ALERT_DATA_GRID_ROW) + .its('length') + .then((count) => { + cy.get(ALERT_ASIGNEES_COLUMN).should('have.length', count); + }); + cy.get(ALERT_ASIGNEES_COLUMN).each(($column) => { + cy.wrap($column).within(() => { + cy.get(ALERT_AVATARS_PANEL).children().should('have.length', 0); + }); + }); +}; + +export const checkEmptyAssigneesStateInAlertDetailsFlyout = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).within(() => { + cy.get(ALERT_AVATARS_PANEL).children().should('have.length', 0); + }); +}; + +export const alertsTableMoreActionsAreNotAvailable = () => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).should('not.exist'); +}; + +export const asigneesMenuItemsAreNotAvailable = (alertIndex = 0) => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(alertIndex).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); +}; + +export const asigneesBulkMenuItemsAreNotAvailable = () => { + selectFirstPageAlerts(); + cy.get(TAKE_ACTION_POPOVER_BTN).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); +}; + +export const cannotAddAssigneesViaDetailsFlyout = () => { + cy.get(ALERT_DETAILS_ASSIGN_BUTTON).should('be.disabled'); +}; + +export const alertsTableShowsAssigneesForAlert = (users: SecurityRoleName[], alertIndex = 0) => { + cy.get(ALERT_ASIGNEES_COLUMN) + .eq(alertIndex) + .within(() => { + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('exist')); + }); +}; + +export const alertsTableShowsAssigneesForAllAlerts = (users: SecurityRoleName[]) => { + cy.get(ALERT_ASIGNEES_COLUMN).each(($column) => { + cy.wrap($column).within(() => { + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('exist')); + }); + }); +}; + +export const alertsTableShowsAssigneesBadgeForAlert = ( + users: SecurityRoleName[], + alertIndex = 0 +) => { + cy.get(ALERT_ASIGNEES_COLUMN) + .eq(alertIndex) + .within(() => { + cy.get(ALERT_ASSIGNEES_COUNT_BADGE).contains(users.length); + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('not.exist')); + }); +}; + +export const alertDetailsFlyoutShowsAssignees = (users: SecurityRoleName[]) => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).within(() => { + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('exist')); + }); +}; + +export const alertDetailsFlyoutShowsAssigneesBadge = (users: SecurityRoleName[]) => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).within(() => { + cy.get(ALERT_ASSIGNEES_COUNT_BADGE).contains(users.length); + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('not.exist')); + }); +}; + +export const selectAlertAssignee = (assignee: string) => { + cy.get(ALERT_ASSIGNEES_SELECT_PANEL).within(() => { + if (assignee === NO_ASSIGNEES) { + cy.get(ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM).contains(assignee).click(); + return; + } + cy.get('input').type(assignee); + cy.get(ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM).contains(assignee).click(); + cy.get(ALERT_USERS_PROFILES_CLEAR_SEARCH_BUTTON).click(); + }); +}; + +/** + * This will update assignees for selected alert + * @param users The list of assugnees to update. If assignee is not assigned yet it will be assigned, otherwise it will be unassigned + * @param alertIndex The index of the alert in the alerts table + */ +export const updateAssigneesForAlert = (users: SecurityRoleName[], alertIndex = 0) => { + openAlertAssigningActionMenu(alertIndex); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const updateAssigneesViaAddButtonInFlyout = (users: SecurityRoleName[]) => { + cy.get(ALERT_DETAILS_ASSIGN_BUTTON).click(); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const updateAssigneesViaTakeActionButtonInFlyout = (users: SecurityRoleName[]) => { + cy.get(ALERT_DETAILS_TAKE_ACTION_BUTTON).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).click(); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const bulkUpdateAssignees = (users: SecurityRoleName[]) => { + openAlertAssigningBulkActionMenu(); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const removeAllAssigneesForAlert = (alertIndex = 0) => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(alertIndex).click(); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).click(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const removeAllAssigneesViaTakeActionButtonInFlyout = () => { + cy.get(ALERT_DETAILS_TAKE_ACTION_BUTTON).click(); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).click(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const bulkRemoveAllAssignees = () => { + cy.get(TAKE_ACTION_POPOVER_BTN).click(); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).click(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const filterByAssignees = (users: Array) => { + cy.get(FILTER_BY_ASSIGNEES_BUTTON).scrollIntoView(); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); + users.forEach((user) => selectAlertAssignee(user)); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); +}; + +export const clearAssigneesFilter = () => { + cy.get(FILTER_BY_ASSIGNEES_BUTTON).scrollIntoView(); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); + cy.get(ALERT_ASSIGNEES_SELECT_PANEL).within(() => { + cy.contains('Clear filters').click(); + }); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); +}; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts index 90729abd4e25c..bcc3177846d37 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts @@ -82,8 +82,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); }; - // FLAKY: https://github.com/elastic/kibana/issues/170435 - describe.skip('Response Actions Responder', function () { + describe('Response Actions Responder', function () { targetTags(this, ['@ess', '@serverless']); let indexedData: IndexedHostsAndAlertsResponse; @@ -197,8 +196,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.clickWhenNotDisabled('endpointResponseActions-action-item'); await testSubjects.existOrFail('consolePageOverlay'); - await performResponderSanityChecks(); + // close tour popup + if (await testSubjects.exists('timeline-save-tour-close-button')) { + await testSubjects.click('timeline-save-tour-close-button'); + } + await performResponderSanityChecks(); await pageObjects.timeline.closeTimeline(); }); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts index 79f964f8c4271..5a13045af0ba3 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts @@ -10,10 +10,11 @@ import { ACTION_DETAILS_ROUTE, ACTION_STATUS_ROUTE, AGENT_POLICY_SUMMARY_ROUTE, - BASE_POLICY_RESPONSE_ROUTE, BASE_ENDPOINT_ACTION_ROUTE, - GET_PROCESSES_ROUTE, + BASE_POLICY_RESPONSE_ROUTE, + EXECUTE_ROUTE, GET_FILE_ROUTE, + GET_PROCESSES_ROUTE, HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, ISOLATE_HOST_ROUTE_V2, @@ -21,7 +22,6 @@ import { METADATA_TRANSFORMS_STATUS_ROUTE, SUSPEND_PROCESS_ROUTE, UNISOLATE_HOST_ROUTE_V2, - EXECUTE_ROUTE, } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; import { targetTags } from '../../security_solution_endpoint/target_tags'; @@ -40,12 +40,7 @@ export default function ({ getService }: FtrProviderContext) { body: Record | undefined; } - // Flaky: - // https://github.com/elastic/kibana/issues/171655 - // https://github.com/elastic/kibana/issues/171656 - // https://github.com/elastic/kibana/issues/171647 - // https://github.com/elastic/kibana/issues/171648 - describe.skip('When attempting to call an endpoint api', function () { + describe('When attempting to call an endpoint api', function () { targetTags(this, ['@ess', '@serverless']); let indexedData: IndexedHostsAndAlertsResponse; diff --git a/yarn.lock b/yarn.lock index 2e609e33d4ae3..a544bef1859d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1577,19 +1577,17 @@ dependencies: tslib "^1.9.3" -"@elastic/ecs-helpers@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@elastic/ecs-helpers/-/ecs-helpers-1.1.0.tgz#ee7e6f870f75a2222c5d7179b36a628f1db4779e" - integrity sha512-MDLb2aFeGjg46O5mLpdCzT5yOUDnXToJSrco2ShqGIXxNJaM8uJjX+4nd+hRYV4Vex8YJyDtOFEVBldQct6ndg== - dependencies: - fast-json-stringify "^2.4.1" +"@elastic/ecs-helpers@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@elastic/ecs-helpers/-/ecs-helpers-2.1.1.tgz#8a375b307c33a959938d9ae8f9abb466eb9fb3bf" + integrity sha512-ItoNazMnYdlUCmkBYTXc3SG6PF7UlVTbvMdHPvXkfTMPdwGv2G1Xtp5CjDHaGHGOZSwaDrW4RSCXvA/lMSU+rg== -"@elastic/ecs-pino-format@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@elastic/ecs-pino-format/-/ecs-pino-format-1.2.0.tgz#3ee709eb2343b4d1a7a6d23bc467673d8c0de2c2" - integrity sha512-7TGPoxPMHkhqdp98u9F1+4aNwktgh8tlG/PX2c/d/RcAqHziaRCc72tuwGLMu9K/w/M5bWz0eKbcFXr4fSZGwg== +"@elastic/ecs-pino-format@^1.2.0", "@elastic/ecs-pino-format@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@elastic/ecs-pino-format/-/ecs-pino-format-1.5.0.tgz#48610e06e939b50bfa3629da0d2fcb1c74a69a20" + integrity sha512-7MMVmT50ucEl7no8mUgCIl+pffBVNRl36uZi0vmalWa2xPWISBxM9k9WSP/WTgOkmGj9G35e5g3UfCS1zxshBg== dependencies: - "@elastic/ecs-helpers" "^1.1.0" + "@elastic/ecs-helpers" "^2.1.1" "@elastic/elasticsearch-types@npm:@elastic/elasticsearch@^8.2.1": version "8.6.0" @@ -5145,6 +5143,10 @@ version "0.0.0" uid "" +"@kbn/observability-get-padded-alert-time-range-util@link:x-pack/packages/observability/get_padded_alert_time_range_util": + version "0.0.0" + uid "" + "@kbn/observability-log-explorer-plugin@link:x-pack/plugins/observability_log_explorer": version "0.0.0" uid "" @@ -8841,10 +8843,10 @@ resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.1.3.tgz#0b03d737ff28fad10eb884e0c6cedd5ffdc4ba0a" integrity sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g== -"@types/chromedriver@^81.0.2": - version "81.0.2" - resolved "https://registry.yarnpkg.com/@types/chromedriver/-/chromedriver-81.0.2.tgz#b01a1007f9b39804e8ebaed07b2b86a33a21e121" - integrity sha512-sWozcf88uN5B5hh9wuLupSY1JRuN551NjhbNTK+4DGp1c1qiQGprpPy+hJkWuckQcEzyDAN3BbOezXfefWAI/Q== +"@types/chromedriver@^81.0.5": + version "81.0.5" + resolved "https://registry.yarnpkg.com/@types/chromedriver/-/chromedriver-81.0.5.tgz#c7b82f45c1cb9ebe47b7fb24a8641c9adf181b67" + integrity sha512-VwV+WTTFHYZotBn57QQ8gd4TE7CGJ15KPM+xJJrKbiQQSccTY7zVXuConSBlyWrO+AFpVxuzmluK3xvzxGmkCw== dependencies: "@types/node" "*" @@ -9938,10 +9940,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/selenium-webdriver@^4.1.13": - version "4.1.13" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.13.tgz#c31e833a3e67a2d2c9e46da3aac8b6acbffc838a" - integrity sha512-kGpIh7bvu4HGCJXl4PEJ53kzpG4iXlRDd66SNNCfJ58QhFuk9skOm57lVffZap5ChEOJwbge/LJ9IVGVC8EEOg== +"@types/selenium-webdriver@^4.1.20": + version "4.1.20" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.20.tgz#00d10f0593c18fe72fabc55b8f62fa387a31a193" + integrity sha512-WxzARWDZVTbXlJgwYGhNoiV4OuHDabctSQmK5V88LqjW9TJiLETcknxRZ2xB1toecQnu0T2jt1pPXnSYkaWYiw== dependencies: "@types/ws" "*" @@ -10355,15 +10357,15 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@wdio/logger@^8.6.6": - version "8.6.6" - resolved "https://registry.yarnpkg.com/@wdio/logger/-/logger-8.6.6.tgz#6f3844a2506730ae1e4151dca0ed0242b5b69b63" - integrity sha512-MS+Y5yqFGx2zVXMOfuBQAVdFsP4DuYz+/hM552xwiDWjGg6EZHoccqUYgH3J5zpu3JFpYV3R/a5jExFiGGck6g== +"@wdio/logger@^8.11.0": + version "8.16.17" + resolved "https://registry.yarnpkg.com/@wdio/logger/-/logger-8.16.17.tgz#c2055857ed3e3cf12cfad843140fa79264c6a632" + integrity sha512-zeQ41z3T+b4IsrriZZipayXxLNDuGsm7TdExaviNGojPVrIsQUCSd/FvlLHM32b7ZrMyInHenu/zx1cjAZO71g== dependencies: chalk "^5.1.2" loglevel "^1.6.0" loglevel-plugin-prefix "^0.8.4" - strip-ansi "^6.0.0" + strip-ansi "^7.1.0" "@webassemblyjs/ast@1.11.1": version "1.11.1" @@ -10903,7 +10905,7 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -13486,6 +13488,11 @@ cookie@^0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + cookiejar@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" @@ -15304,12 +15311,12 @@ elastic-apm-node@3.46.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.1.0.tgz#b1154a2d8e17b7762badf4fc696d8de7439ce928" - integrity sha512-8t9lbyfi4WUPxjPvRNO80QX2Ysf8I+D21wq+aphY+97Fk7kk6SDeZH+5U+o7HWSbqZpo/PYJGuKDUYc9PXuEWw== +elastic-apm-node@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.2.0.tgz#0b9fe675b94902734965d3b9da5424260e4e45a2" + integrity sha512-GbSjtQ+Q9lBZpY6bikatPCL7cMWPuBxHQ2yD0msfuP7JYXhWon42L8N7cqyepmp/F/YF++X8WJxuZM2G7J3kYQ== dependencies: - "@elastic/ecs-pino-format" "^1.2.0" + "@elastic/ecs-pino-format" "^1.4.0" "@opentelemetry/api" "^1.4.1" "@opentelemetry/core" "^1.11.0" "@opentelemetry/sdk-metrics" "^1.12.0" @@ -15318,7 +15325,7 @@ elastic-apm-node@^4.1.0: async-value-promise "^1.1.1" basic-auth "^2.0.1" breadth-filter "^2.0.0" - cookie "^0.5.0" + cookie "^0.6.0" core-util-is "^1.0.2" end-of-stream "^1.4.4" error-callsites "^2.0.4" @@ -16573,16 +16580,6 @@ fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0, fast-json- resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-json-stringify@^2.4.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.6.0.tgz#3dcb4835b63d4e17dbd17411594aa63df8c0f95b" - integrity sha512-xTZtZRopWp2Aun7sGX2EB2mFw4bMQ+xnR8BmD5Rn4K0hKXGkbcZAzTtxEX0P4KNaNx1RAwvf+FESfuM0+F4WZg== - dependencies: - ajv "^6.11.0" - deepmerge "^4.2.2" - rfdc "^1.2.0" - string-similarity "^4.0.1" - fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -17342,19 +17339,19 @@ gaze@^1.0.0: dependencies: globule "^1.0.0" -geckodriver@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-4.0.0.tgz#e078518f1b259b9c746bc4cea440dc9480abdbb2" - integrity sha512-1R1cuqqz+gqZ4Eux//TSbYDBpyYHHs9xLzG6BWJolORlaGkKWbg5qN3415fIi/5IkS7kTA2BEYvplB2GjH8oyw== +geckodriver@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-4.2.1.tgz#03ad628241417737b962966aa8f8b13fa0f8bf75" + integrity sha512-4m/CRk0OI8MaANRuFIahvOxYTSjlNAO2p9JmE14zxueknq6cdtB5M9UGRQ8R9aMV0bLGNVHHDnDXmoXdOwJfWg== dependencies: - "@wdio/logger" "^8.6.6" + "@wdio/logger" "^8.11.0" decamelize "^6.0.0" - http-proxy-agent "^6.0.1" - https-proxy-agent "^6.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" node-fetch "^3.3.1" - tar-fs "^2.1.1" + tar-fs "^3.0.4" unzipper "^0.10.14" - which "^3.0.1" + which "^4.0.0" gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -18500,14 +18497,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-proxy-agent@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-6.1.0.tgz#9bbaebd7d5afc8fae04a5820932b487405b95978" - integrity sha512-75t5ACHLOMnz/KsDAS4BdHx4J0sneT/HW+Sz070NR+n7RZ7SmYXYn2FXq6D0XwQid8hYgRVf6HZJrYuGzaEqtw== - dependencies: - agent-base "^7.0.2" - debug "^4.3.4" - http-proxy-agent@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz#e9096c5afd071a3fce56e6252bb321583c124673" @@ -18566,14 +18555,6 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" -https-proxy-agent@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-6.2.0.tgz#58c525a299663d958556969a8e3536dd1e007485" - integrity sha512-4xhCnMpxR9fupa7leh9uJK2P/qjYIeaM9uZ9c1bi1JDSwX2VH9NDk/oKSToNX4gBKa2WT31Mldne7e26ckohLQ== - dependencies: - agent-base "^7.0.2" - debug "4" - https-proxy-agent@^7.0.1, https-proxy-agent@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" @@ -26660,11 +26641,6 @@ rfc4648@^1.5.2: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.2.tgz#cf5dac417dd83e7f4debf52e3797a723c1373383" integrity sha512-tLOizhR6YGovrEBLatX1sdcuhoSCXddw3mqNVAcKxGJ+J0hFeJ+SjeWCv5UPA/WU3YzWPPuCVYgXBKZUPGpKtg== -rfdc@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - rgbcolor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" @@ -27003,14 +26979,14 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-webdriver@^4.9.1: - version "4.9.1" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.9.1.tgz#24c4055258b8be50792186fe52b60cd00f768481" - integrity sha512-6OBMaSXwn3/vzQJkfYpAjGaYAA+otsE7zu0omz74HQ9XBuAMUImdVS6hKXlB2G3Bt9WIuVfK0F4V8oye8JRc3Q== +selenium-webdriver@^4.15.0: + version "4.15.0" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.15.0.tgz#ee827f8993864dc0821df0d3b46d4f312d6f1aa3" + integrity sha512-BNG1bq+KWiBGHcJ/wULi0eKY0yaDqFIbEmtbsYJmfaEghdCkXBsx1akgOorhNwjBipOr0uwpvNXqT6/nzl+zjg== dependencies: jszip "^3.10.1" tmp "^0.2.1" - ws ">=8.13.0" + ws ">=8.14.2" selfsigned@^2.0.1: version "2.0.1" @@ -28053,11 +28029,6 @@ string-replace-loader@^2.2.0: loader-utils "^1.2.3" schema-utils "^1.0.0" -string-similarity@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21" - integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ== - "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -28212,7 +28183,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^7.0.1: +strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== @@ -30927,13 +30898,6 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -which@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/which/-/which-3.0.1.tgz#89f1cd0c23f629a8105ffe69b8172791c87b4be1" - integrity sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg== - dependencies: - isexe "^2.0.0" - which@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" @@ -31098,7 +31062,7 @@ write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.14.2, ws@>=8.13.0, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: +ws@8.14.2, ws@>=8.14.2, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: version "8.14.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==