diff --git a/.buildkite/scripts/common/setup_job_env.sh b/.buildkite/scripts/common/setup_job_env.sh index bc1d902af781..b2e3bfdd024d 100644 --- a/.buildkite/scripts/common/setup_job_env.sh +++ b/.buildkite/scripts/common/setup_job_env.sh @@ -8,6 +8,8 @@ if [[ "$(type -t vault_get)" != "function" ]]; then source .buildkite/scripts/common/vault_fns.sh fi +source .buildkite/scripts/common/util.sh + # Set up general-purpose tokens and credentials { BUILDKITE_TOKEN="$(vault_get buildkite-ci buildkite_token_all_jobs)" @@ -18,9 +20,12 @@ fi KIBANA_DOCKER_USERNAME="$(vault_get container-registry username)" KIBANA_DOCKER_PASSWORD="$(vault_get container-registry password)" - if (command -v docker && docker version) &> /dev/null; then - echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co - fi + function docker_login() { + if (command -v docker && docker version) &> /dev/null; then + echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co + fi + } + retry 5 15 docker_login } # Set up a custom ES Snapshot Manifest if one has been specified for this build diff --git a/.buildkite/scripts/common/util.sh b/.buildkite/scripts/common/util.sh index dce418180c10..924fedde3ea3 100755 --- a/.buildkite/scripts/common/util.sh +++ b/.buildkite/scripts/common/util.sh @@ -178,3 +178,32 @@ print_if_dry_run() { echo "DRY_RUN is enabled." fi } + +docker_with_retry () { + cmd=$1 + shift + args=("$@") + attempt=0 + max_retries=5 + sleep_time=15 + + while true + do + attempt=$((attempt+1)) + + if [ $attempt -gt $max_retries ] + then + echo "Docker $cmd retries exceeded, aborting." + exit 1 + fi + + if docker "$cmd" "${args[@]}" + then + echo "Docker $cmd successful." + break + else + echo "Docker $cmd unsuccessful, attempt '$attempt'... Retrying in $sleep_time" + sleep $sleep_time + fi + done +} diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh index 1491c25d079d..a494c2d89b97 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh @@ -8,6 +8,7 @@ fi .buildkite/scripts/bootstrap.sh source .buildkite/scripts/steps/artifacts/env.sh +source .buildkite/scripts/common/util.sh GIT_ABBREV_COMMIT=${BUILDKITE_COMMIT:0:12} KIBANA_IMAGE_TAG="sec-sol-qg-$GIT_ABBREV_COMMIT" @@ -23,7 +24,7 @@ if docker manifest inspect $KIBANA_IMAGE &> /dev/null; then exit 0 fi -docker pull $KIBANA_BASE_IMAGE:latest +docker_with_retry pull $KIBANA_BASE_IMAGE:latest echo "--- Build images" node scripts/build \ @@ -50,8 +51,8 @@ docker load < "target/kibana-serverless-$BASE_VERSION-docker-image-aarch64.tar.g docker tag "$KIBANA_IMAGE" "$KIBANA_IMAGE-arm64" echo "--- Push images" -docker image push "$KIBANA_IMAGE-arm64" -docker image push "$KIBANA_IMAGE-amd64" +docker_with_retry push "$KIBANA_IMAGE-arm64" +docker_with_retry push "$KIBANA_IMAGE-amd64" echo "--- Create and push manifests" docker manifest create \ diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh index 3f83ffe74bdb..1918d3e630fc 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh @@ -1,5 +1,7 @@ #!/bin/bash +source .buildkite/scripts/common/util.sh + if [ "$KIBANA_MKI_QUALITY_GATE" == "1" ]; then echo "Triggered by quality gate!" triggered_by="Serverless Quality Gate." @@ -17,11 +19,11 @@ else KBN_IMAGE=${KIBANA_LATEST} fi -docker pull ${KBN_IMAGE} +docker_with_retry pull ${KBN_IMAGE} build_date=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.build-date"') vcs_ref=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.vcs-ref"') vcs_url=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.vcs-url"') -version=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.version"') +version=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.version"') markdown_text=""" #### $triggered_by @@ -32,9 +34,9 @@ markdown_text=""" --- #### Kibana Container Metadata -- Build Date : $build_date -- Github Commit Hash : $vcs_ref -- Github Repo : $vcs_url +- Build Date : $build_date +- Github Commit Hash : $vcs_ref +- Github Repo : $vcs_url - Version : $version """ -echo "${markdown_text//[*\\_]/\\&}" | buildkite-agent annotate --style "info" \ No newline at end of file +echo "${markdown_text//[*\\_]/\\&}" | buildkite-agent annotate --style "info" diff --git a/.buildkite/scripts/steps/artifacts/cloud.sh b/.buildkite/scripts/steps/artifacts/cloud.sh index 86fa86f37bd3..bc28ceabd1c0 100644 --- a/.buildkite/scripts/steps/artifacts/cloud.sh +++ b/.buildkite/scripts/steps/artifacts/cloud.sh @@ -28,7 +28,7 @@ docker tag "$KIBANA_BASE_IMAGE" "$KIBANA_TEST_IMAGE" if docker manifest inspect $KIBANA_TEST_IMAGE &> /dev/null; then echo "Cloud image already exists, skipping docker push" else - docker image push "$KIBANA_TEST_IMAGE" + docker_with_retry push "$KIBANA_TEST_IMAGE" fi echo "--- Create deployment" diff --git a/.buildkite/scripts/steps/artifacts/docker_image.sh b/.buildkite/scripts/steps/artifacts/docker_image.sh index a148bdb805f6..558fc7d02a1c 100755 --- a/.buildkite/scripts/steps/artifacts/docker_image.sh +++ b/.buildkite/scripts/steps/artifacts/docker_image.sh @@ -6,6 +6,8 @@ set -euo pipefail source .buildkite/scripts/steps/artifacts/env.sh +source .buildkite/scripts/common/util.sh + GIT_ABBREV_COMMIT=${BUILDKITE_COMMIT:0:12} if [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" ]]; then KIBANA_IMAGE_TAG="git-$GIT_ABBREV_COMMIT" @@ -50,8 +52,8 @@ if [[ "$SKIP_BUILD" == "false" ]]; then docker tag "$KIBANA_IMAGE" "$KIBANA_IMAGE-arm64" echo "--- Push images" - docker image push "$KIBANA_IMAGE-arm64" - docker image push "$KIBANA_IMAGE-amd64" + docker_with_retry push "$KIBANA_IMAGE-arm64" + docker_with_retry push "$KIBANA_IMAGE-amd64" echo "--- Create and push manifests" docker manifest create \ diff --git a/.buildkite/scripts/steps/artifacts/publish.sh b/.buildkite/scripts/steps/artifacts/publish.sh index 52e58ebb5013..8a5ba6ea50be 100644 --- a/.buildkite/scripts/steps/artifacts/publish.sh +++ b/.buildkite/scripts/steps/artifacts/publish.sh @@ -54,7 +54,7 @@ chmod -R a+r target/* chmod -R a+w target echo "--- Pull latest Release Manager CLI" -docker pull docker.elastic.co/infra/release-manager:latest +docker_with_retry pull docker.elastic.co/infra/release-manager:latest echo "--- Publish artifacts" if [[ "$BUILDKITE_BRANCH" == "$KIBANA_BASE_BRANCH" ]] || [[ "${DRY_RUN:-}" =~ ^(1|true)$ ]]; then diff --git a/.buildkite/scripts/steps/demo_env/es_and_init.sh b/.buildkite/scripts/steps/demo_env/es_and_init.sh index e0364a1fcc7c..33a0f5e6946c 100755 --- a/.buildkite/scripts/steps/demo_env/es_and_init.sh +++ b/.buildkite/scripts/steps/demo_env/es_and_init.sh @@ -3,6 +3,7 @@ set -euo pipefail source "$(dirname "${0}")/config.sh" +source "$(dirname "${0}")/../../common/util.sh" "$(dirname "${0}")/auth.sh" @@ -16,7 +17,7 @@ DOCKER_EXPORT_URL=$(curl https://storage.googleapis.com/kibana-ci-es-snapshots-d curl "$DOCKER_EXPORT_URL" > target/elasticsearch-docker.tar.gz docker load < target/elasticsearch-docker.tar.gz docker tag "docker.elastic.co/elasticsearch/elasticsearch:$DEPLOYMENT_VERSION-SNAPSHOT" "$ES_IMAGE" -docker push "$ES_IMAGE" +docker_with_retry push "$ES_IMAGE" echo '--- Prepare yaml' diff --git a/.buildkite/scripts/steps/demo_env/kibana.sh b/.buildkite/scripts/steps/demo_env/kibana.sh index e8dacdfb94e5..b10fba726a31 100755 --- a/.buildkite/scripts/steps/demo_env/kibana.sh +++ b/.buildkite/scripts/steps/demo_env/kibana.sh @@ -4,6 +4,8 @@ set -euo pipefail .buildkite/scripts/bootstrap.sh +source .buildkite/scripts/common/util.sh + source "$(dirname "${0}")/config.sh" export KIBANA_IMAGE="gcr.io/elastic-kibana-184716/demo/kibana:$DEPLOYMENT_NAME-$(git rev-parse HEAD)" @@ -15,7 +17,7 @@ echo '--- Build Docker image with example plugins' cd target/example_plugins BUILT_IMAGE="docker.elastic.co/kibana/kibana:$DEPLOYMENT_VERSION-SNAPSHOT" docker build --build-arg BASE_IMAGE="$BUILT_IMAGE" -t "$KIBANA_IMAGE" -f "$KIBANA_DIR/.buildkite/scripts/steps/demo_env/Dockerfile" . -docker push "$KIBANA_IMAGE" +docker_with_retry push "$KIBANA_IMAGE" cd - "$(dirname "${0}")/auth.sh" diff --git a/.buildkite/scripts/steps/es_serverless/annotate_runtime_parameters.sh b/.buildkite/scripts/steps/es_serverless/annotate_runtime_parameters.sh index c3cc571f8a4d..4bfedcc477eb 100644 --- a/.buildkite/scripts/steps/es_serverless/annotate_runtime_parameters.sh +++ b/.buildkite/scripts/steps/es_serverless/annotate_runtime_parameters.sh @@ -2,6 +2,8 @@ set -euo pipefail +source .buildkite/scripts/common/util.sh + KIBANA_GITHUB_URL="https://github.com/elastic/kibana" ES_SERVERLESS_GITHUB_URL="https://github.com/elastic/elasticsearch-serverless" @@ -15,7 +17,7 @@ fi # Pull the target image if [[ $ES_SERVERLESS_IMAGE != *":git-"* ]]; then - docker pull "$ES_SERVERLESS_IMAGE" + docker_with_retry pull "$ES_SERVERLESS_IMAGE" ES_SERVERLESS_VERSION=$(docker inspect --format='{{json .Config.Labels}}' "$ES_SERVERLESS_IMAGE" | jq -r '.["org.opencontainers.image.revision"]' | cut -c1-12) IMAGE_WITHOUT_TAG=$(echo "$ES_SERVERLESS_IMAGE" | cut -d: -f1) diff --git a/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh b/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh index 6ff62e7cdc1b..6b70799ec809 100755 --- a/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh +++ b/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh @@ -31,18 +31,18 @@ ARM_64_DIGEST=$(jq -r '.manifests[] | select(.platform.architecture == "arm64") AMD_64_DIGEST=$(jq -r '.manifests[] | select(.platform.architecture == "amd64") | .digest' manifests.json) echo docker pull --platform linux/arm64 "$SOURCE_IMAGE@$ARM_64_DIGEST" -docker pull --platform linux/arm64 "$SOURCE_IMAGE@$ARM_64_DIGEST" +docker_with_retry pull --platform linux/arm64 "$SOURCE_IMAGE@$ARM_64_DIGEST" echo linux/arm64 image pulled, with digest: $ARM_64_DIGEST echo docker pull --platform linux/amd64 "$SOURCE_IMAGE@$AMD_64_DIGEST" -docker pull --platform linux/amd64 "$SOURCE_IMAGE@$AMD_64_DIGEST" +docker_with_retry pull --platform linux/amd64 "$SOURCE_IMAGE@$AMD_64_DIGEST" echo linux/amd64 image pulled, with digest: $AMD_64_DIGEST docker tag "$SOURCE_IMAGE@$ARM_64_DIGEST" "$TARGET_IMAGE-arm64" docker tag "$SOURCE_IMAGE@$AMD_64_DIGEST" "$TARGET_IMAGE-amd64" -docker push "$TARGET_IMAGE-arm64" -docker push "$TARGET_IMAGE-amd64" +docker_with_retry push "$TARGET_IMAGE-arm64" +docker_with_retry push "$TARGET_IMAGE-amd64" docker manifest rm "$TARGET_IMAGE" || echo "Nothing to delete" diff --git a/.buildkite/scripts/steps/es_snapshots/build.sh b/.buildkite/scripts/steps/es_snapshots/build.sh index 8e239e476c62..d9d685338250 100755 --- a/.buildkite/scripts/steps/es_snapshots/build.sh +++ b/.buildkite/scripts/steps/es_snapshots/build.sh @@ -93,7 +93,7 @@ set +e echo $ES_CLOUD_ID $ES_CLOUD_VERSION $KIBANA_ES_CLOUD_VERSION $KIBANA_ES_CLOUD_IMAGE docker tag "$ES_CLOUD_ID" "$KIBANA_ES_CLOUD_IMAGE" - docker image push "$KIBANA_ES_CLOUD_IMAGE" + docker_with_retry push "$KIBANA_ES_CLOUD_IMAGE" export ELASTICSEARCH_CLOUD_IMAGE="$KIBANA_ES_CLOUD_IMAGE" export ELASTICSEARCH_CLOUD_IMAGE_CHECKSUM="$(docker images "$KIBANA_ES_CLOUD_IMAGE" --format "{{.Digest}}")" diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index f80b6cdab904..44801b4052df 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -63,6 +63,9 @@ border-left: 0; border-right: 0; } + .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='additionalRowControl_menuControl'] .euiDataGridRowCell__content { + padding-bottom: 0; + } .euiDataGridHeaderCell.euiDataGridHeaderCell--controlColumn[data-gridcell-column-id='select'] { padding-left: $euiSizeXS; diff --git a/src/plugins/discover/public/application/context/context_app.test.tsx b/src/plugins/discover/public/application/context/context_app.test.tsx index 42ef78f6f757..ccecd2d9b566 100644 --- a/src/plugins/discover/public/application/context/context_app.test.tsx +++ b/src/plugins/discover/public/application/context/context_app.test.tsx @@ -24,11 +24,13 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import type { HistoryLocationState } from '../../build_services'; import { createSearchSessionMock } from '../../__mocks__/search_session'; +import { createDiscoverServicesMock } from '../../__mocks__/services'; const mockFilterManager = createFilterManagerMock(); const mockNavigationPlugin = { ui: { TopNavMenu: mockTopNavMenu, AggregateQueryTopNavMenu: mockTopNavMenu }, }; +const discoverServices = createDiscoverServicesMock(); describe('ContextApp test', () => { const { history } = createSearchSessionMock(); @@ -53,6 +55,7 @@ describe('ContextApp test', () => { toastNotifications: { addDanger: () => {} }, navigation: mockNavigationPlugin, core: { + ...discoverServices.core, executionContext: { set: jest.fn(), }, @@ -75,6 +78,7 @@ describe('ContextApp test', () => { }, contextLocator: { getRedirectUrl: jest.fn(() => '') }, singleDocLocator: { getRedirectUrl: jest.fn(() => '') }, + profilesManager: discoverServices.profilesManager, } as unknown as DiscoverServices; const defaultProps = { diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx index e0f0dbbd820c..54c2be693df3 100644 --- a/src/plugins/discover/public/application/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.tsx @@ -40,6 +40,7 @@ import { DocTableContext } from '../../components/doc_table/doc_table_context'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { DiscoverGridFlyout } from '../../components/discover_grid_flyout'; import { onResizeGridColumn } from '../../utils/on_resize_grid_column'; +import { useProfileAccessor } from '../../context_awareness'; export interface ContextAppContentProps { columns: string[]; @@ -159,6 +160,12 @@ export function ContextAppContent({ [grid, setAppState] ); + const getCellRenderersAccessor = useProfileAccessor('getCellRenderers'); + const cellRenderers = useMemo(() => { + const getCellRenderers = getCellRenderersAccessor(() => ({})); + return getCellRenderers(); + }, [getCellRenderersAccessor]); + return ( @@ -222,6 +229,7 @@ export function ContextAppContent({ configHeaderRowHeight={3} settings={grid} onResize={onResize} + externalCustomRenderers={cellRenderers} /> diff --git a/src/plugins/discover/public/application/context/services/anchor.ts b/src/plugins/discover/public/application/context/services/anchor.ts index b04ca0a0f4cf..926044ebd4f2 100644 --- a/src/plugins/discover/public/application/context/services/anchor.ts +++ b/src/plugins/discover/public/application/context/services/anchor.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { lastValueFrom } from 'rxjs'; +import { firstValueFrom, lastValueFrom } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { ISearchSource, EsQuerySortValue } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -14,6 +14,7 @@ import { buildDataTableRecord } from '@kbn/discover-utils'; import type { DataTableRecord, EsHitRecord } from '@kbn/discover-utils/types'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import type { DiscoverServices } from '../../../build_services'; +import { createDataViewDataSource } from '../../../../common/data_sources'; export async function fetchAnchor( anchorId: string, @@ -26,6 +27,16 @@ export async function fetchAnchor( anchorRow: DataTableRecord; interceptedWarnings: SearchResponseWarning[]; }> { + const { core, profilesManager } = services; + + const solutionNavId = await firstValueFrom(core.chrome.getActiveSolutionNavId$()); + await profilesManager.resolveRootProfile({ solutionNavId }); + await profilesManager.resolveDataSourceProfile({ + dataSource: dataView?.id ? createDataViewDataSource({ dataViewId: dataView.id }) : undefined, + dataView, + query: { query: '', language: 'kuery' }, + }); + updateSearchSource(searchSource, anchorId, sort, useNewFieldsApi, dataView); const adapter = new RequestAdapter(); @@ -55,7 +66,9 @@ export async function fetchAnchor( }); return { - anchorRow: buildDataTableRecord(doc, dataView, true), + anchorRow: profilesManager.resolveDocumentProfile({ + record: buildDataTableRecord(doc, dataView, true), + }), interceptedWarnings, }; } diff --git a/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts b/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts index bb99a305b7c6..22af251d65ca 100644 --- a/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts +++ b/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts @@ -46,6 +46,7 @@ export async function fetchHitsInInterval( rows: DataTableRecord[]; interceptedWarnings: SearchResponseWarning[]; }> { + const { profilesManager } = services; const range: RangeQuery = { format: 'strict_date_optional_time', }; @@ -96,7 +97,11 @@ export async function fetchHitsInInterval( const { rawResponse } = await lastValueFrom(fetch$); const dataView = searchSource.getField('index'); - const rows = rawResponse.hits?.hits.map((hit) => buildDataTableRecord(hit, dataView!)); + const rows = rawResponse.hits?.hits.map((hit) => + profilesManager.resolveDocumentProfile({ + record: buildDataTableRecord(hit, dataView!), + }) + ); const interceptedWarnings: SearchResponseWarning[] = []; services.data.search.showWarnings(adapter, (warning) => { interceptedWarnings.push(warning); 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 79a1be2cf175..262b5819f939 100644 --- a/src/plugins/discover/public/application/doc/components/doc.test.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.test.tsx @@ -19,7 +19,9 @@ 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'; +import { createDiscoverServicesMock } from '../../../__mocks__/services'; +const discoverServices = createDiscoverServicesMock(); const mockSearchApi = jest.fn(); beforeEach(() => { @@ -68,6 +70,8 @@ async function mountDoc(update = false) { }, locator: { getUrl: jest.fn(() => Promise.resolve('mock-url')) }, chrome: { setBreadcrumbs: jest.fn() }, + profilesManager: discoverServices.profilesManager, + core: discoverServices.core, }; setUnifiedDocViewerServices({ ...mockUnifiedDocViewerServices, diff --git a/src/plugins/discover/public/application/doc/components/doc.tsx b/src/plugins/discover/public/application/doc/components/doc.tsx index c604abcdd74d..eb706e59e2a3 100644 --- a/src/plugins/discover/public/application/doc/components/doc.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.tsx @@ -6,15 +6,18 @@ * Side Public License, v 1. */ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { firstValueFrom } from 'rxjs'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPage, EuiPageBody } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ElasticRequestState } from '@kbn/unified-doc-viewer'; -import { UnifiedDocViewer, useEsDocSearch } from '@kbn/unified-doc-viewer-plugin/public'; +import { useEsDocSearch } from '@kbn/unified-doc-viewer-plugin/public'; import type { EsDocSearchProps } from '@kbn/unified-doc-viewer-plugin/public/types'; import { setBreadcrumbs } from '../../../utils/breadcrumbs'; import { useDiscoverServices } from '../../../hooks/use_discover_services'; +import { SingleDocViewer } from './single_doc_viewer'; +import { createDataViewDataSource } from '../../../../common/data_sources'; export interface DocProps extends EsDocSearchProps { /** @@ -25,11 +28,33 @@ export interface DocProps extends EsDocSearchProps { export function Doc(props: DocProps) { const { dataView } = props; - const [reqState, hit] = useEsDocSearch(props); const services = useDiscoverServices(); - const { locator, chrome, docLinks } = services; + const { locator, chrome, docLinks, core, profilesManager } = services; const indexExistsLink = docLinks.links.apis.indexExists; + const onBeforeFetch = useCallback(async () => { + const solutionNavId = await firstValueFrom(core.chrome.getActiveSolutionNavId$()); + await profilesManager.resolveRootProfile({ solutionNavId }); + await profilesManager.resolveDataSourceProfile({ + dataSource: dataView?.id ? createDataViewDataSource({ dataViewId: dataView.id }) : undefined, + dataView, + query: { query: '', language: 'kuery' }, + }); + }, [profilesManager, core, dataView]); + + const onProcessRecord = useCallback( + (record) => { + return profilesManager.resolveDocumentProfile({ record }); + }, + [profilesManager] + ); + + const [reqState, record] = useEsDocSearch({ + ...props, + onBeforeFetch, + onProcessRecord, + }); + useEffect(() => { setBreadcrumbs({ services, @@ -117,9 +142,9 @@ export function Doc(props: DocProps) { )} - {reqState === ElasticRequestState.Found && hit !== null && dataView && ( + {reqState === ElasticRequestState.Found && record !== null && dataView && (
- +
)} diff --git a/src/plugins/discover/public/application/doc/components/single_doc_viewer.tsx b/src/plugins/discover/public/application/doc/components/single_doc_viewer.tsx new file mode 100644 index 000000000000..d22970c1635d --- /dev/null +++ b/src/plugins/discover/public/application/doc/components/single_doc_viewer.tsx @@ -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 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 React, { useMemo } from 'react'; +import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; +import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { useProfileAccessor } from '../../../context_awareness'; + +interface SingleDocViewerProps { + record: DataTableRecord; + dataView: DataView; +} + +export const SingleDocViewer: React.FC = ({ record, dataView }) => { + const getDocViewerAccessor = useProfileAccessor('getDocViewer', { + record, + }); + const docViewer = useMemo(() => { + const getDocViewer = getDocViewerAccessor(() => ({ + title: undefined, + docViewsRegistry: (registry: DocViewsRegistry) => registry, + })); + + return getDocViewer({ record }); + }, [getDocViewerAccessor, record]); + + return ( + + ); +}; 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 57c816a2c801..091df99c2e22 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 @@ -39,6 +39,15 @@ export interface EsDocSearchProps { * Records fetched from text based query */ textBasedHits?: DataTableRecord[]; + /** + * An optional callback that will be called before fetching the doc + */ + onBeforeFetch?: () => Promise; + /** + * An optional callback that will be called after fetching the doc + * @param record + */ + onProcessRecord?: (record: DataTableRecord) => DataTableRecord; } /** @@ -50,6 +59,8 @@ export function useEsDocSearch({ dataView, requestSource, textBasedHits, + onBeforeFetch, + onProcessRecord, }: EsDocSearchProps): [ElasticRequestState, DataTableRecord | null, () => void] { const [status, setStatus] = useState(ElasticRequestState.Loading); const [hit, setHit] = useState(null); @@ -63,6 +74,9 @@ export function useEsDocSearch({ const singleDocFetchingStartTime = window.performance.now(); try { + if (onBeforeFetch) { + await onBeforeFetch(); + } const result = await lastValueFrom( data.search.search({ params: { @@ -77,7 +91,8 @@ export function useEsDocSearch({ if (hits?.hits?.[0]) { setStatus(ElasticRequestState.Found); - setHit(buildDataTableRecord(hits.hits[0], dataView)); + const record = buildDataTableRecord(hits?.hits?.[0], dataView); + setHit(onProcessRecord ? onProcessRecord(record) : record); } else { setStatus(ElasticRequestState.NotFound); } @@ -98,7 +113,17 @@ export function useEsDocSearch({ duration: singleDocFetchingDuration, }); } - }, [analytics, data.search, dataView, id, index, useNewFieldsApi, requestSource]); + }, [ + analytics, + data.search, + dataView, + id, + index, + useNewFieldsApi, + requestSource, + onBeforeFetch, + onProcessRecord, + ]); useEffect(() => { if (textBasedHits) { diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts b/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts index 00531b80f4a7..0bd0523365d8 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts @@ -11,12 +11,13 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList']); + const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList', 'header']); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); const dataViews = getService('dataViews'); const queryBar = getService('queryBar'); + const browser = getService('browser'); describe('extension getCellRenderers', () => { before(async () => { @@ -76,8 +77,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.submitQuery(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); - const firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); - const logLevelBadge = await firstCell.findByTestSubject('*logLevelBadgeCell-'); + let firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + let logLevelBadge = await firstCell.findByTestSubject('*logLevelBadgeCell-'); + expect(await logLevelBadge.getVisibleText()).to.be('debug'); + expect(await logLevelBadge.getComputedStyle('background-color')).to.be( + 'rgba(190, 207, 227, 1)' + ); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + logLevelBadge = await firstCell.findByTestSubject('*logLevelBadgeCell-'); expect(await logLevelBadge.getVisibleText()).to.be('debug'); expect(await logLevelBadge.getComputedStyle('background-color')).to.be( 'rgba(190, 207, 227, 1)' @@ -93,7 +109,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.submitQuery(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); - const firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + let firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + expect(await firstCell.getVisibleText()).to.be('debug'); + await testSubjects.missingOrFail('*logLevelBadgeCell-'); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + firstCell = await dataGrid.getCellElementExcludingControlColumns(1, 1); expect(await firstCell.getVisibleText()).to.be('debug'); await testSubjects.missingOrFail('*logLevelBadgeCell-'); }); diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts b/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts index e2c91143d53f..b73984270313 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts @@ -10,10 +10,11 @@ import kbnRison from '@kbn/rison'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover']); + const PageObjects = getPageObjects(['common', 'discover', 'header']); const testSubjects = getService('testSubjects'); const dataViews = getService('dataViews'); const dataGrid = getService('dataGrid'); + const browser = getService('browser'); describe('extension getDocViewer', () => { describe('ES|QL mode', () => { @@ -60,6 +61,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); await dataGrid.clickDocViewerTab('doc_view_logs_overview'); await testSubjects.existOrFail('unifiedDocViewLogsOverviewHeader'); + + // check Surrounding docs page + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await dataGrid.clickRowToggle({ isAnchorRow: true }); + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); + await dataGrid.clickDocViewerTab('doc_view_logs_overview'); + await testSubjects.existOrFail('unifiedDocViewLogsOverviewHeader'); + + // check Single doc page + const [singleDocActionEl] = await dataGrid.getRowActions(); + await singleDocActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); + await dataGrid.clickDocViewerTab('doc_view_logs_overview'); + await testSubjects.existOrFail('unifiedDocViewLogsOverviewHeader'); }); it('should not render logs overview tab for non-logs data source', async () => { @@ -71,6 +97,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle(); await testSubjects.existOrFail('docViewerTab-doc_view_table'); await testSubjects.missingOrFail('docViewerTab-doc_view_logs_overview'); + + // check Surrounding docs page + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await dataGrid.clickRowToggle({ isAnchorRow: true }); + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.missingOrFail('docViewerTab-doc_view_logs_overview'); + + // check Single doc page + const [singleDocActionEl] = await dataGrid.getRowActions(); + await singleDocActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.missingOrFail('docViewerTab-doc_view_logs_overview'); }); }); }); diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts index be536fd6cdbe..533acd8fc128 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts @@ -10,9 +10,11 @@ import kbnRison from '@kbn/rison'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover']); + const PageObjects = getPageObjects(['common', 'discover', 'header']); const testSubjects = getService('testSubjects'); const dataViews = getService('dataViews'); + const dataGrid = getService('dataGrid'); + const browser = getService('browser'); describe('extension getRowAdditionalLeadingControls', () => { describe('ES|QL mode', () => { @@ -50,6 +52,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); }); it('should not render logs controls for non-logs data source', async () => { @@ -58,6 +71,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); }); }); }); diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts b/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts index 8efa852cbfb2..05250147cb55 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts @@ -11,11 +11,18 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'unifiedFieldList']); + const PageObjects = getPageObjects([ + 'common', + 'timePicker', + 'discover', + 'unifiedFieldList', + 'header', + ]); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); const browser = getService('browser'); + const dataViews = getService('dataViews'); describe('extension getRowIndicatorProvider', () => { before(async () => { @@ -91,5 +98,54 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(await secondColorIndicator.getAttribute('title')).to.be('Error'); }); + + it('should render log.level row indicators on Surrounding documents page', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs,logstash*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await dataGrid.clickRowToggle({ rowIndex: 0 }); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + let anchorCell = await dataGrid.getCellElement(0, 0); + let anchorColorIndicator = await anchorCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await anchorColorIndicator.getAttribute('title')).to.be('Debug'); + expect(await anchorColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(190, 207, 227, 1)' + ); + + let nextCell = await dataGrid.getCellElement(1, 0); + let nextColorIndicator = await nextCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await nextColorIndicator.getAttribute('title')).to.be('Error'); + expect(await nextColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(223, 147, 82, 1)' + ); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + anchorCell = await dataGrid.getCellElement(0, 0); + anchorColorIndicator = await anchorCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await anchorColorIndicator.getAttribute('title')).to.be('Debug'); + expect(await anchorColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(190, 207, 227, 1)' + ); + + nextCell = await dataGrid.getCellElement(1, 0); + nextColorIndicator = await nextCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await nextColorIndicator.getAttribute('title')).to.be('Error'); + expect(await nextColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(223, 147, 82, 1)' + ); + }); }); } diff --git a/test/functional/apps/discover/group1/_doc_accessibility.ts b/test/functional/apps/discover/group1/_doc_accessibility.ts index 5ecdef656b2a..e6600c179ba5 100644 --- a/test/functional/apps/discover/group1/_doc_accessibility.ts +++ b/test/functional/apps/discover/group1/_doc_accessibility.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.pressKeys(browser.keys.TAB); await browser.pressKeys(browser.keys.SPACE); await browser.pressKeys(browser.keys.TAB); - const tableTab = await testSubjects.find('docViewerTab-doc_view_table'); + const tableTab = await testSubjects.find('docViewerTab-doc_view_logs_overview'); const activeElement = await find.activeElement(); expect(await tableTab.getAttribute('data-test-subj')).to.eql( await activeElement.getAttribute('data-test-subj') diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx index e51619a38f14..c2643ffa9e92 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx @@ -34,7 +34,7 @@ import * as i18n from './translations'; /** * List of allowed action type IDs for the integrations assistant. */ -const AllowedActionTypeIds = ['.bedrock']; +const AllowedActionTypeIds = ['.bedrock', '.gen-ai', '.gemini']; interface ConnectorStepProps { connector: AIConnector | undefined; diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/translations.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/translations.ts index d26b13e0db7e..30f78279d158 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/translations.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/translations.ts @@ -29,6 +29,6 @@ export const SUPPORTED_MODELS_INFO = i18n.translate( 'xpack.integrationAssistant.steps.connector.supportedModelsInfo', { defaultMessage: - "Automatic Import currently supports Anthropic models via Elastic's connector for Amazon Bedrock. Support for additional LLMs will be introduced soon", + "We currently recommend using a provider that supports the newer Claude models for the best experience. We're currently working on adding better support for GPT-4 and similar models", } ); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.test.ts index 51aab586253a..e0d1ed1e832f 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.test.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.test.ts @@ -12,6 +12,9 @@ describe('test chunks', () => { const objects = ['{"a": 1, "b": 2, "c": {"d": 3}}', '{"a": 2, "b": 3, "e": 4}']; const chunkSize = 2; const result = mergeAndChunkSamples(objects, chunkSize); - expect(result).toEqual(['{"a":1,"b":2}', '{"c":{"d":3},"e":4}']); + expect(result).toStrictEqual([ + JSON.stringify({ a: 1, b: 2 }, null, 2), + JSON.stringify({ c: { d: 3 }, e: 4 }, null, 2), + ]); }); }); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.ts index 62448776bdf8..ca114f114e51 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/chunk.ts @@ -27,7 +27,7 @@ export function mergeAndChunkSamples(objects: string[], chunkSize: number): stri const chunks = generateChunks(result, chunkSize); // Each chunk is used for the combinedSamples state when passed to the subgraph, which should be a nicely formatted string - return chunks.map((chunk) => JSON.stringify(chunk)); + return chunks.map((chunk) => JSON.stringify(chunk, null, 2)); } // This function takes the already merged array of samples, and splits it up into chunks of a given size. diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts index 573b704ae453..62cce199cfe0 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts @@ -99,9 +99,6 @@ export const ECS_TYPES: EcsFields = { 'container.network.ingress.bytes': 'long', 'container.runtime': 'keyword', 'container.security_context.privileged': 'boolean', - 'data_stream.dataset': 'constant_keyword', - 'data_stream.namespace': 'constant_keyword', - 'data_stream.type': 'constant_keyword', 'destination.address': 'keyword', 'destination.as.number': 'long', 'destination.as.organization.name': 'keyword', @@ -237,14 +234,12 @@ export const ECS_TYPES: EcsFields = { 'event.category': 'keyword', 'event.code': 'keyword', 'event.created': 'date', - 'event.dataset': 'keyword', 'event.duration': 'long', 'event.end': 'date', 'event.hash': 'keyword', 'event.id': 'keyword', 'event.ingested': 'date', 'event.kind': 'keyword', - 'event.module': 'keyword', 'event.original': 'keyword', 'event.outcome': 'keyword', 'event.provider': 'keyword', @@ -1722,9 +1717,6 @@ export const ECS_FIELDS: EcsFields = { 'container.labels': 'Image labels.', 'container.name': 'Container name.', 'container.runtime': 'Runtime managing this container.', - 'data_stream.dataset': 'Data stream dataset.', - 'data_stream.namespace': 'Data stream namespace.', - 'data_stream.type': 'Data stream type.', 'destination.address': 'Destination network address.', 'destination.bytes': 'Bytes sent from the destination to the source.', 'destination.domain': 'Destination domain.', @@ -2175,74 +2167,6 @@ export const ECS_EXAMPLE_ANSWER = { type: 'string', date_formats: [], }, - CommandLine: { - target: 'process.command_line', - confidence: 0.9, - type: 'string', - date_formats: [], - }, - ConnectionDirection: { - target: 'network.direction', - confidence: 0.9, - type: 'string', - date_formats: [], - }, - EventType: { - target: 'event.action', - confidence: 0.82, - type: 'string', - date_formats: [], - }, - Flags: { Audit: null, Log: null, Monitor: null }, - HostName: { - target: 'host.name', - confidence: 0.82, - type: 'string', - date_formats: [], - }, - LocalAddress: { - target: 'source.address', - confidence: 0.83, - type: 'string', - date_formats: [], - }, - LocalPort: { - target: 'source.port', - confidence: 0.83, - type: 'number', - date_formats: [], - }, - PolicyName: null, - RemoteAddress: { - target: 'destination.address', - confidence: 0.83, - type: 'string', - date_formats: [], - }, - RemotePort: { - target: 'destination.port', - confidence: 0.83, - type: 'number', - date_formats: [], - }, - RuleAction: { - target: 'event.type', - confidence: 0.86, - type: 'string', - date_formats: [], - }, - RuleDescription: { - target: 'rule.description', - confidence: 0.99, - type: 'string', - date_formats: [], - }, - UTCTimestamp: { - target: '@timestamp', - confidence: 0.99, - type: 'string', - date_formats: ['UNIX_MS'], - }, }, }, }, diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts index 5df21295c8e2..fab18e0decdb 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts @@ -41,10 +41,10 @@ Go through each value step by step and modify it with the following process: You ALWAYS follow these guidelines when writing your response: - Never use \`event.category\` or \`event.type\` as target ECS fields. -- The target key should never have a null value, if no matching target ECS field is found, the whole key value should be set to null. +- The key named "target" should never have a null value or a "null" string, if no matching target ECS field is found the original source key's value should be set to null.. - Never use the same ECS target multiple times. If no other field is found that you are confident in, it should always be null. - All keys should be under the {package_name} {data_stream_name} parent fields, same as the original combined sample above. -- All target key values should be ECS field names only from the above ECS fields provided as context. +- All values for the key named "target" should be ECS field names only from the above ECS fields provided as context. - All original keys from the combined sample object needs to be in your response. - Only when a target value is set should type, date_format and confidence be filled out. If no target value then the value should simply be null. - Do not respond with anything except the ecs maping JSON object enclosed with 3 backticks (\`), see example response below. diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.test.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.test.ts index 5008f5fa3ef3..7c52919a5636 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.test.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.test.ts @@ -5,25 +5,25 @@ * 2.0. */ -import { FakeLLM } from '@langchain/core/utils/testing'; -import { handleLogFormatDetection } from './detection'; -import type { LogFormatDetectionState } from '../../types'; -import { logFormatDetectionTestState } from '../../../__jest__/fixtures/log_type_detection'; import { ActionsClientChatOpenAI, ActionsClientSimpleChatModel, } from '@kbn/langchain/server/language_models'; +import { FakeLLM } from '@langchain/core/utils/testing'; +import { logFormatDetectionTestState } from '../../../__jest__/fixtures/log_type_detection'; +import type { LogFormatDetectionState } from '../../types'; +import { handleLogFormatDetection } from './detection'; -const mockLLM = new FakeLLM({ +const model = new FakeLLM({ response: '{ "log_type": "structured"}', }) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; -const testState: LogFormatDetectionState = logFormatDetectionTestState; +const state: LogFormatDetectionState = logFormatDetectionTestState; describe('Testing log type detection handler', () => { it('handleLogFormatDetection()', async () => { - const response = await handleLogFormatDetection(testState, mockLLM); - expect(response.logFormat).toStrictEqual('structured'); + const response = await handleLogFormatDetection({ state, model }); + expect(response.samplesFormat).toStrictEqual('structured'); expect(response.lastExecutedChain).toBe('logFormatDetection'); }); }); diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts index c41b66263c7f..f4fe70e72ee4 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/detection.ts @@ -4,20 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { - ActionsClientChatOpenAI, - ActionsClientSimpleChatModel, -} from '@kbn/langchain/server/language_models'; import { JsonOutputParser } from '@langchain/core/output_parsers'; import type { LogFormatDetectionState } from '../../types'; import { LOG_FORMAT_DETECTION_PROMPT } from './prompts'; +import type { LogDetectionNodeParams } from './types'; const MaxLogSamplesInPrompt = 5; -export async function handleLogFormatDetection( - state: LogFormatDetectionState, - model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel -) { +export async function handleLogFormatDetection({ + state, + model, +}: LogDetectionNodeParams): Promise> { const outputParser = new JsonOutputParser(); const logFormatDetectionNode = LOG_FORMAT_DETECTION_PROMPT.pipe(model).pipe(outputParser); @@ -30,7 +27,7 @@ export async function handleLogFormatDetection( ex_answer: state.exAnswer, log_samples: samples, }); - const logFormat = detectedLogFormatAnswer.log_type; + const samplesFormat = detectedLogFormatAnswer.log_type; - return { logFormat, lastExecutedChain: 'logFormatDetection' }; + return { samplesFormat, lastExecutedChain: 'logFormatDetection' }; } diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.test.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.test.ts index 3a14239a1c8f..caec6b53f158 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.test.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.test.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { FakeLLM } from '@langchain/core/utils/testing'; -import { getLogFormatDetectionGraph } from './graph'; import { ActionsClientChatOpenAI, ActionsClientSimpleChatModel, } from '@kbn/langchain/server/language_models'; +import { FakeLLM } from '@langchain/core/utils/testing'; +import { getLogFormatDetectionGraph } from './graph'; -const mockLLM = new FakeLLM({ +const model = new FakeLLM({ response: '{"log_type": "structured"}', }) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; @@ -22,7 +22,7 @@ describe('LogFormatDetectionGraph', () => { // When getLogFormatDetectionGraph runs, langgraph compiles the graph it will error if the graph has any issues. // Common issues for example detecting a node has no next step, or there is a infinite loop between them. try { - await getLogFormatDetectionGraph(mockLLM); + await getLogFormatDetectionGraph({ model }); } catch (error) { fail(`getLogFormatDetectionGraph threw an error: ${error}`); } diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.ts index e0773b556e84..e997607eee1b 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/graph.ts @@ -5,16 +5,13 @@ * 2.0. */ -import type { - ActionsClientChatOpenAI, - ActionsClientSimpleChatModel, -} from '@kbn/langchain/server/language_models'; import type { StateGraphArgs } from '@langchain/langgraph'; import { END, START, StateGraph } from '@langchain/langgraph'; +import { SamplesFormat } from '../../../common'; import type { LogFormatDetectionState } from '../../types'; import { EX_ANSWER_LOG_TYPE } from './constants'; import { handleLogFormatDetection } from './detection'; -import { SamplesFormat } from '../../../common'; +import type { LogDetectionBaseNodeParams, LogDetectionGraphParams } from './types'; const graphState: StateGraphArgs['channels'] = { lastExecutedChain: { @@ -47,7 +44,7 @@ const graphState: StateGraphArgs['channels'] = { }, }; -function modelInput(state: LogFormatDetectionState): Partial { +function modelInput({ state }: LogDetectionBaseNodeParams): Partial { return { exAnswer: JSON.stringify(EX_ANSWER_LOG_TYPE, null, 2), finalized: false, @@ -55,7 +52,7 @@ function modelInput(state: LogFormatDetectionState): Partial { +function modelOutput({ state }: LogDetectionBaseNodeParams): Partial { return { finalized: true, lastExecutedChain: 'modelOutput', @@ -66,7 +63,7 @@ function modelOutput(state: LogFormatDetectionState): Partial modelInput({ state })) + .addNode('modelOutput', (state: LogFormatDetectionState) => modelOutput({ state })) .addNode('handleLogFormatDetection', (state: LogFormatDetectionState) => - handleLogFormatDetection(state, model) + handleLogFormatDetection({ state, model }) ) - // .addNode('handleKVGraph', (state: LogFormatDetectionState) => getCompiledKvGraph(state, model)) - // .addNode('handleUnstructuredGraph', (state: LogFormatDetectionState) => getCompiledUnstructuredGraph(state, model)) - // .addNode('handleCsvGraph', (state: LogFormatDetectionState) => getCompiledCsvGraph(state, model)) + // .addNode('handleKVGraph', (state: LogFormatDetectionState) => getCompiledKvGraph({state, model})) + // .addNode('handleUnstructuredGraph', (state: LogFormatDetectionState) => getCompiledUnstructuredGraph({state, model})) + // .addNode('handleCsvGraph', (state: LogFormatDetectionState) => getCompiledCsvGraph({state, model})) .addEdge(START, 'modelInput') .addEdge('modelInput', 'handleLogFormatDetection') .addEdge('modelOutput', END) - .addConditionalEdges('handleLogFormatDetection', logFormatRouter, { - // TODO: Add structured, unstructured, csv nodes - // structured: 'handleKVGraph', - // unstructured: 'handleUnstructuredGraph', - // csv: 'handleCsvGraph', - unsupported: 'modelOutput', - }); + .addConditionalEdges( + 'handleLogFormatDetection', + (state: LogFormatDetectionState) => logFormatRouter({ state }), + { + // TODO: Add structured, unstructured, csv nodes + // structured: 'handleKVGraph', + // unstructured: 'handleUnstructuredGraph', + // csv: 'handleCsvGraph', + unsupported: 'modelOutput', + } + ); const compiledLogFormatDetectionGraph = workflow.compile(); diff --git a/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/types.ts b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/types.ts new file mode 100644 index 000000000000..d5ef15c0f3a1 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/log_type_detection/types.ts @@ -0,0 +1,20 @@ +/* + * 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 { ChatModels, LogFormatDetectionState } from '../../types'; + +export interface LogDetectionBaseNodeParams { + state: LogFormatDetectionState; +} + +export interface LogDetectionNodeParams extends LogDetectionBaseNodeParams { + model: ChatModels; +} + +export interface LogDetectionGraphParams { + model: ChatModels; +} diff --git a/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts b/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts index 85826e56c200..e919ef48dd7d 100644 --- a/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts @@ -7,18 +7,15 @@ import type { IKibanaResponse, IRouter } from '@kbn/core/server'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { - ActionsClientChatOpenAI, - ActionsClientSimpleChatModel, -} from '@kbn/langchain/server/language_models'; import { APMTracer } from '@kbn/langchain/server/tracers/apm'; import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; import { ANALYZE_LOGS_PATH, AnalyzeLogsRequestBody, AnalyzeLogsResponse } from '../../common'; import { ROUTE_HANDLER_TIMEOUT } from '../constants'; +import { getLogFormatDetectionGraph } from '../graphs/log_type_detection/graph'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { getLLMClass, getLLMType } from '../util/llm'; import { buildRouteValidationWithZod } from '../util/route_validation'; import { withAvailability } from './with_availability'; -import { getLogFormatDetectionGraph } from '../graphs/log_type_detection/graph'; export function registerAnalyzeLogsRoutes( router: IRouter @@ -48,19 +45,19 @@ export function registerAnalyzeLogsRoutes( const [, { actions: actionsPlugin }] = await getStartServices(); try { const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); - const connector = req.body.connectorId - ? await actionsClient.get({ id: req.body.connectorId }) - : (await actionsClient.getAll()).filter( - (connectorItem) => connectorItem.actionTypeId === '.bedrock' - )[0]; + const connector = await actionsClient.get({ id: req.body.connectorId }); + const abortSignal = getRequestAbortedSignal(req.events.aborted$); - const isOpenAI = connector.actionTypeId === '.gen-ai'; - const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + + const actionTypeId = connector.actionTypeId; + const llmType = getLLMType(actionTypeId); + const llmClass = getLLMClass(llmType); + const model = new llmClass({ actionsClient, connectorId: connector.id, logger, - llmType: isOpenAI ? 'openai' : 'bedrock', + llmType, model: connector.config?.defaultModel, temperature: 0.05, maxTokens: 4096, @@ -77,7 +74,7 @@ export function registerAnalyzeLogsRoutes( const logFormatParameters = { logSamples, }; - const graph = await getLogFormatDetectionGraph(model); + const graph = await getLogFormatDetectionGraph({ model }); const graphResults = await graph.invoke(logFormatParameters, options); const graphLogFormat = graphResults.results.samplesFormat.name; if (graphLogFormat === 'unsupported') { diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts index 439ebe91db2b..a886d3721f49 100644 --- a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts @@ -7,10 +7,6 @@ import type { IKibanaResponse, IRouter } from '@kbn/core/server'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { - ActionsClientChatOpenAI, - ActionsClientSimpleChatModel, -} from '@kbn/langchain/server/language_models'; import { APMTracer } from '@kbn/langchain/server/tracers/apm'; import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; import { @@ -21,6 +17,7 @@ import { import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import { getCategorizationGraph } from '../graphs/categorization'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { getLLMClass, getLLMType } from '../util/llm'; import { buildRouteValidationWithZod } from '../util/route_validation'; import { withAvailability } from './with_availability'; @@ -57,21 +54,19 @@ export function registerCategorizationRoutes( try { const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); - const connector = req.body.connectorId - ? await actionsClient.get({ id: req.body.connectorId }) - : (await actionsClient.getAll()).filter( - (connectorItem) => connectorItem.actionTypeId === '.bedrock' - )[0]; + const connector = await actionsClient.get({ id: req.body.connectorId }); const abortSignal = getRequestAbortedSignal(req.events.aborted$); - const isOpenAI = connector.actionTypeId === '.gen-ai'; - const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + + const actionTypeId = connector.actionTypeId; + const llmType = getLLMType(actionTypeId); + const llmClass = getLLMClass(llmType); const model = new llmClass({ actionsClient, connectorId: connector.id, logger, - llmType: isOpenAI ? 'openai' : 'bedrock', + llmType, model: connector.config?.defaultModel, temperature: 0.05, maxTokens: 4096, diff --git a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts index 78ecf2023858..0db5abee5b8a 100644 --- a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts @@ -7,16 +7,13 @@ import type { IKibanaResponse, IRouter } from '@kbn/core/server'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { - ActionsClientChatOpenAI, - ActionsClientSimpleChatModel, -} from '@kbn/langchain/server/language_models'; import { APMTracer } from '@kbn/langchain/server/tracers/apm'; import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; import { ECS_GRAPH_PATH, EcsMappingRequestBody, EcsMappingResponse } from '../../common'; import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import { getEcsGraph } from '../graphs/ecs'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { getLLMClass, getLLMType } from '../util/llm'; import { buildRouteValidationWithZod } from '../util/route_validation'; import { withAvailability } from './with_availability'; @@ -47,22 +44,19 @@ export function registerEcsRoutes(router: IRouter connectorItem.actionTypeId === '.bedrock' - )[0]; + const connector = await actionsClient.get({ id: req.body.connectorId }); const abortSignal = getRequestAbortedSignal(req.events.aborted$); - const isOpenAI = connector.actionTypeId === '.gen-ai'; - const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + const actionTypeId = connector.actionTypeId; + const llmType = getLLMType(actionTypeId); + const llmClass = getLLMClass(llmType); const model = new llmClass({ actionsClient, connectorId: connector.id, logger, - llmType: isOpenAI ? 'openai' : 'bedrock', + llmType, model: connector.config?.defaultModel, temperature: 0.05, maxTokens: 4096, diff --git a/x-pack/plugins/integration_assistant/server/routes/related_routes.ts b/x-pack/plugins/integration_assistant/server/routes/related_routes.ts index 9574d0cc5894..cc37584b57d6 100644 --- a/x-pack/plugins/integration_assistant/server/routes/related_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/related_routes.ts @@ -7,16 +7,13 @@ import type { IKibanaResponse, IRouter } from '@kbn/core/server'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { - ActionsClientChatOpenAI, - ActionsClientSimpleChatModel, -} from '@kbn/langchain/server/language_models'; import { APMTracer } from '@kbn/langchain/server/tracers/apm'; import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; import { RELATED_GRAPH_PATH, RelatedRequestBody, RelatedResponse } from '../../common'; import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import { getRelatedGraph } from '../graphs/related'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { getLLMClass, getLLMType } from '../util/llm'; import { buildRouteValidationWithZod } from '../util/route_validation'; import { withAvailability } from './with_availability'; @@ -49,21 +46,19 @@ export function registerRelatedRoutes(router: IRouter connectorItem.actionTypeId === '.bedrock' - )[0]; + const connector = await actionsClient.get({ id: req.body.connectorId }); - const isOpenAI = connector.actionTypeId === '.gen-ai'; - const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; const abortSignal = getRequestAbortedSignal(req.events.aborted$); + const actionTypeId = connector.actionTypeId; + const llmType = getLLMType(actionTypeId); + const llmClass = getLLMClass(llmType); + const model = new llmClass({ actionsClient, connectorId: connector.id, logger, - llmType: isOpenAI ? 'openai' : 'bedrock', + llmType, model: connector.config?.defaultModel, temperature: 0.05, maxTokens: 4096, diff --git a/x-pack/plugins/integration_assistant/server/util/llm.ts b/x-pack/plugins/integration_assistant/server/util/llm.ts new file mode 100644 index 000000000000..2b60ad101176 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/llm.ts @@ -0,0 +1,30 @@ +/* + * 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 { + ActionsClientBedrockChatModel, + ActionsClientChatOpenAI, + ActionsClientGeminiChatModel, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server'; +export const getLLMType = (actionTypeId: string): string | undefined => { + const llmTypeDictionary: Record = { + [`.gen-ai`]: `openai`, + [`.bedrock`]: `bedrock`, + [`.gemini`]: `gemini`, + }; + return llmTypeDictionary[actionTypeId]; +}; + +export const getLLMClass = (llmType?: string) => + llmType === 'openai' + ? ActionsClientChatOpenAI + : llmType === 'bedrock' + ? ActionsClientBedrockChatModel + : llmType === 'gemini' + ? ActionsClientGeminiChatModel + : ActionsClientSimpleChatModel; diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts index 73693349d39b..aa0796c584c7 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; +import semver from 'semver'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; import { fold } from 'fp-ts/lib/Either'; @@ -37,6 +38,7 @@ const COMMON_HEADERS = { }; export default function ({ getService }: FtrProviderContext) { + const es = getService('es'); const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); @@ -79,6 +81,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('highlights built-in message column', async () => { + const esInfo = await es.info(); + const highlightTerms = 'message of document 0'; const { body } = await supertest .post(LOG_ENTRIES_HIGHLIGHTS_PATH) .set(COMMON_HEADERS) @@ -87,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, - highlightTerms: ['message of document 0'], + highlightTerms: [highlightTerms], }) ) .expect(200); @@ -120,7 +124,10 @@ export default function ({ getService }: FtrProviderContext) { entries.forEach((entry) => { entry.columns.forEach((column) => { if ('message' in column && 'highlights' in column.message[0]) { - expect(column.message[0].highlights).to.eql(['message of document 0']); + const expectation = semver.gte(esInfo.version.number, '8.10.0') + ? [highlightTerms] + : highlightTerms.split(' '); + expect(column.message[0].highlights).to.eql(expectation); } }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts index 7bce934099e1..4eaca072d3b1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts @@ -10,12 +10,19 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList', 'svlCommonPage']); + const PageObjects = getPageObjects([ + 'common', + 'discover', + 'unifiedFieldList', + 'svlCommonPage', + 'header', + ]); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); const dataViews = getService('dataViews'); const queryBar = getService('queryBar'); + const browser = getService('browser'); describe('extension getCellRenderers', () => { before(async () => { @@ -76,8 +83,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.submitQuery(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); - const firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); - const logLevelBadge = await firstCell.findByTestSubject('*logLevelBadgeCell-'); + let firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + let logLevelBadge = await firstCell.findByTestSubject('*logLevelBadgeCell-'); + expect(await logLevelBadge.getVisibleText()).to.be('debug'); + expect(await logLevelBadge.getComputedStyle('background-color')).to.be( + 'rgba(190, 207, 227, 1)' + ); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + logLevelBadge = await firstCell.findByTestSubject('*logLevelBadgeCell-'); expect(await logLevelBadge.getVisibleText()).to.be('debug'); expect(await logLevelBadge.getComputedStyle('background-color')).to.be( 'rgba(190, 207, 227, 1)' @@ -93,7 +115,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.submitQuery(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); - const firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + let firstCell = await dataGrid.getCellElementExcludingControlColumns(0, 1); + expect(await firstCell.getVisibleText()).to.be('debug'); + await testSubjects.missingOrFail('*logLevelBadgeCell-'); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + firstCell = await dataGrid.getCellElementExcludingControlColumns(1, 1); expect(await firstCell.getVisibleText()).to.be('debug'); await testSubjects.missingOrFail('*logLevelBadgeCell-'); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts index 52b514a6673b..d214d295e1a7 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts @@ -9,10 +9,11 @@ import kbnRison from '@kbn/rison'; import type { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage']); + const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage', 'header']); const testSubjects = getService('testSubjects'); const dataViews = getService('dataViews'); const dataGrid = getService('dataGrid'); + const browser = getService('browser'); describe('extension getDocViewer', () => { before(async () => { @@ -63,6 +64,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); await dataGrid.clickDocViewerTab('doc_view_logs_overview'); await testSubjects.existOrFail('unifiedDocViewLogsOverviewHeader'); + + // check Surrounding docs page + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await dataGrid.clickRowToggle({ isAnchorRow: true }); + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); + await dataGrid.clickDocViewerTab('doc_view_logs_overview'); + await testSubjects.existOrFail('unifiedDocViewLogsOverviewHeader'); + + // check Single doc page + const [singleDocActionEl] = await dataGrid.getRowActions(); + await singleDocActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); + await dataGrid.clickDocViewerTab('doc_view_logs_overview'); + await testSubjects.existOrFail('unifiedDocViewLogsOverviewHeader'); }); it('should not render logs overview tab for non-logs data source', async () => { @@ -74,6 +100,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle(); await testSubjects.existOrFail('docViewerTab-doc_view_table'); await testSubjects.missingOrFail('docViewerTab-doc_view_logs_overview'); + + // check Surrounding docs page + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await dataGrid.clickRowToggle({ isAnchorRow: true }); + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.missingOrFail('docViewerTab-doc_view_logs_overview'); + + // check Single doc page + const [singleDocActionEl] = await dataGrid.getRowActions(); + await singleDocActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('docViewerTab-doc_view_table'); + await testSubjects.missingOrFail('docViewerTab-doc_view_logs_overview'); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts index c91dae10bc4e..d69b155f4472 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts @@ -9,9 +9,11 @@ import kbnRison from '@kbn/rison'; import type { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage']); + const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage', 'header']); const testSubjects = getService('testSubjects'); const dataViews = getService('dataViews'); + const dataGrid = getService('dataGrid'); + const browser = getService('browser'); describe('extension getRowAdditionalLeadingControls', () => { before(async () => { @@ -52,6 +54,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); }); it('should not render logs controls for non-logs data source', async () => { @@ -60,6 +73,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + + // check Surrounding docs page + await dataGrid.clickRowToggle(); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts index c7b402665e68..0a5d4911275f 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts @@ -16,11 +16,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'discover', 'unifiedFieldList', 'svlCommonPage', + 'header', ]); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); const browser = getService('browser'); + const dataViews = getService('dataViews'); describe('extension getRowIndicatorProvider', () => { before(async () => { @@ -88,5 +90,54 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(await firstColorIndicator.getAttribute('title')).to.be('Debug'); }); + + it('should render log.level row indicators on Surrounding documents page', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs,logstash*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await dataGrid.clickRowToggle({ rowIndex: 0 }); + const [, surroundingActionEl] = await dataGrid.getRowActions(); + await surroundingActionEl.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + let anchorCell = await dataGrid.getCellElement(0, 0); + let anchorColorIndicator = await anchorCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await anchorColorIndicator.getAttribute('title')).to.be('Debug'); + expect(await anchorColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(190, 207, 227, 1)' + ); + + let nextCell = await dataGrid.getCellElement(1, 0); + let nextColorIndicator = await nextCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await nextColorIndicator.getAttribute('title')).to.be('Error'); + expect(await nextColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(223, 147, 82, 1)' + ); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + anchorCell = await dataGrid.getCellElement(0, 0); + anchorColorIndicator = await anchorCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await anchorColorIndicator.getAttribute('title')).to.be('Debug'); + expect(await anchorColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(190, 207, 227, 1)' + ); + + nextCell = await dataGrid.getCellElement(1, 0); + nextColorIndicator = await nextCell.findByTestSubject( + 'unifiedDataTableRowColorIndicatorCell' + ); + expect(await nextColorIndicator.getAttribute('title')).to.be('Error'); + expect(await nextColorIndicator.getComputedStyle('background-color')).to.be( + 'rgba(223, 147, 82, 1)' + ); + }); }); }