From 70eb1939ae09a095a9e8011ab2e4b5f55044beb9 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 17 Apr 2023 08:52:33 -0400 Subject: [PATCH 1/8] [Fleet] Fix output ca_trusted_fingerprint validation message (#154651) --- .../edit_output_flyout/output_form_validators.test.tsx | 4 +++- .../components/edit_output_flyout/output_form_validators.tsx | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx index ea0e1541fb41c..6b806fe1f20bd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx @@ -118,7 +118,9 @@ describe('Output form validation', () => { '9F:0A:10:41:14:57:AD:DE:39:82:EF:01:DF:20:D2:E7:AA:53:A8:EF:29:C5:0B:CB:FA:3F:3E:93:AE:BF:63:1B' ); - expect(res).toEqual(['CA trusted fingerprint should be a base64 CA sha256 fingerprint']); + expect(res).toEqual([ + 'CA trusted fingerprint should be valid HEX encoded SHA-256 of a CA certificate', + ]); }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx index d76bb1ca7d035..870b1f74b9bca 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx @@ -154,7 +154,8 @@ export function validateCATrustedFingerPrint(value: string) { if (value !== '' && !value.match(/^[a-zA-Z0-9]+$/)) { return [ i18n.translate('xpack.fleet.settings.outputForm.caTrusterdFingerprintInvalidErrorMessage', { - defaultMessage: 'CA trusted fingerprint should be a base64 CA sha256 fingerprint', + defaultMessage: + 'CA trusted fingerprint should be valid HEX encoded SHA-256 of a CA certificate', }), ]; } From bbf8b70db25d2ec46c64e4dba19892d92167bed6 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Mon, 17 Apr 2023 15:00:00 +0200 Subject: [PATCH 2/8] Add URL parsing for SLO Creation Form (#154933) --- .../slo_edit/components/slo_edit_form.tsx | 17 ++- .../public/pages/slo_edit/slo_edit.test.tsx | 137 ++++++++++++++++++ 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx index 57e118b9050d5..f27d6299a548c 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useHistory } from 'react-router-dom'; import { EuiButton, EuiCheckbox, @@ -17,7 +17,8 @@ import { EuiSteps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import type { CreateSLOInput, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { useKibana } from '../../../utils/kibana_react'; import { useCreateSlo } from '../../../hooks/slo/use_create_slo'; @@ -53,7 +54,17 @@ export function SloEditForm({ slo }: Props) { triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, } = useKibana().services; + const history = useHistory(); const { search } = useLocation(); + + const urlStateStorage = createKbnUrlStateStorage({ + history, + useHash: false, + useHashQuery: false, + }); + + const urlParams = urlStateStorage.get('_a'); + const searchParams = new URLSearchParams(search); const isEditMode = slo !== undefined; @@ -66,7 +77,7 @@ export function SloEditForm({ slo }: Props) { } const methods = useForm({ - defaultValues: SLO_EDIT_FORM_DEFAULT_VALUES, + defaultValues: { ...SLO_EDIT_FORM_DEFAULT_VALUES, ...urlParams }, values: transformSloResponseToCreateSloInput(slo), mode: 'all', }); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx index 42a58c112e3d0..73b386e54ca16 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import Router from 'react-router-dom'; +import { createBrowserHistory } from 'history'; import { waitFor, fireEvent, screen } from '@testing-library/dom'; import { cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -18,6 +19,7 @@ import { useFetchIndices } from '../../hooks/use_fetch_indices'; import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details'; import { useCreateSlo } from '../../hooks/slo/use_create_slo'; import { useUpdateSlo } from '../../hooks/slo/use_update_slo'; +import { useFetchApmSuggestions } from '../../hooks/slo/use_fetch_apm_suggestions'; import { kibanaStartMock } from '../../utils/kibana_react.mock'; import { buildSlo } from '../../data/slo/slo'; import { paths } from '../../config/paths'; @@ -34,6 +36,7 @@ jest.mock('../../hooks/use_fetch_indices'); jest.mock('../../hooks/slo/use_fetch_slo_details'); jest.mock('../../hooks/slo/use_create_slo'); jest.mock('../../hooks/slo/use_update_slo'); +jest.mock('../../hooks/slo/use_fetch_apm_suggestions'); const mockUseKibanaReturnValue = kibanaStartMock.startContract(); @@ -47,6 +50,7 @@ const useFetchIndicesMock = useFetchIndices as jest.Mock; const useFetchSloMock = useFetchSloDetails as jest.Mock; const useCreateSloMock = useCreateSlo as jest.Mock; const useUpdateSloMock = useUpdateSlo as jest.Mock; +const useFetchApmSuggestionsMock = useFetchApmSuggestions as jest.Mock; const mockAddSuccess = jest.fn(); const mockAddError = jest.fn(); @@ -294,6 +298,64 @@ describe('SLO Edit Page', () => { } `); }); + + it('prefills the form with values when URL Search parameters are passed', () => { + jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: undefined }); + + const history = createBrowserHistory(); + history.push( + '/slos/create?_a=(name:%27prefilledSloName%27,indicator:(params:(environment:prod,service:cartService),type:sli.apm.transactionDuration))' + ); + jest.spyOn(Router, 'useHistory').mockReturnValue(history); + jest + .spyOn(Router, 'useLocation') + .mockReturnValue({ pathname: 'foo', search: '', state: '', hash: '' }); + + useFetchSloMock.mockReturnValue({ isLoading: false, slo: undefined }); + + useFetchApmSuggestionsMock.mockReturnValue({ + suggestions: ['cartService'], + isLoading: false, + }); + + useFetchIndicesMock.mockReturnValue({ + isLoading: false, + indices: [{ name: 'some-index' }], + }); + + useCreateSloMock.mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + isSuccess: false, + isError: false, + }); + + useUpdateSloMock.mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + isSuccess: false, + isError: false, + }); + + render(); + + expect(screen.queryByTestId('slosEditPage')).toBeTruthy(); + expect(screen.queryByTestId('sloForm')).toBeTruthy(); + + expect(screen.queryByTestId('sloEditFormIndicatorSection')).toBeTruthy(); + // Show default values from the kql indicator + expect(screen.queryByTestId('sloFormIndicatorTypeSelect')).toHaveValue( + 'sli.apm.transactionDuration' + ); + + expect(screen.queryByTestId('sloEditFormObjectiveSection')).toBeTruthy(); + expect(screen.queryByTestId('sloEditFormDescriptionSection')).toBeTruthy(); + + expect(screen.queryByTestId('apmLatencyServiceSelector')).toHaveTextContent('cartService'); + expect(screen.queryByTestId('apmLatencyEnvironmentSelector')).toHaveTextContent('prod'); + + expect(screen.queryByTestId('sloFormNameInput')).toHaveValue('prefilledSloName'); + }); }); describe('when a sloId route param is provided', () => { @@ -399,6 +461,81 @@ describe('SLO Edit Page', () => { expect(mockUpdate).toMatchInlineSnapshot(`[MockFunction]`); }); + + it('does not prefill the form with URL Search parameters when they are passed', () => { + const slo = buildSlo({ id: '123' }); + + jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); + + const history = createBrowserHistory(); + history.push( + '/slos/create?_a=(name:%27prefilledSloName%27,indicator:(params:(environment:prod,service:cartService),type:sli.apm.transactionDuration))' + ); + jest.spyOn(Router, 'useHistory').mockReturnValue(history); + jest + .spyOn(Router, 'useLocation') + .mockReturnValue({ pathname: 'foo', search: '', state: '', hash: '' }); + + useFetchSloMock.mockReturnValue({ isLoading: false, slo }); + + useFetchApmSuggestionsMock.mockReturnValue({ + suggestions: ['cartService'], + isLoading: false, + }); + + useFetchIndicesMock.mockReturnValue({ + isLoading: false, + indices: [{ name: 'some-index' }], + }); + + useCreateSloMock.mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + isSuccess: false, + isError: false, + }); + + useUpdateSloMock.mockReturnValue({ + mutateAsync: jest.fn(), + isLoading: false, + isSuccess: false, + isError: false, + }); + + render(); + + // all sections are visible + expect(screen.queryByTestId('sloEditFormIndicatorSection')).toBeTruthy(); + expect(screen.queryByTestId('sloEditFormObjectiveSection')).toBeTruthy(); + expect(screen.queryByTestId('sloEditFormDescriptionSection')).toBeTruthy(); + + expect(screen.queryByTestId('sloFormIndicatorTypeSelect')).toHaveValue(slo.indicator.type); + expect(screen.queryByTestId('indexSelectionSelectedValue')).toHaveTextContent( + slo.indicator.params.index! + ); + expect(screen.queryByTestId('customKqlIndicatorFormQueryFilterInput')).toHaveValue( + slo.indicator.type === 'sli.kql.custom' ? slo.indicator.params.filter : '' + ); + expect(screen.queryByTestId('customKqlIndicatorFormGoodQueryInput')).toHaveValue( + slo.indicator.type === 'sli.kql.custom' ? slo.indicator.params.good : '' + ); + expect(screen.queryByTestId('customKqlIndicatorFormTotalQueryInput')).toHaveValue( + slo.indicator.type === 'sli.kql.custom' ? slo.indicator.params.total : '' + ); + + expect(screen.queryByTestId('sloFormBudgetingMethodSelect')).toHaveValue( + slo.budgetingMethod + ); + expect(screen.queryByTestId('sloFormTimeWindowDurationSelect')).toHaveValue( + slo.timeWindow.duration + ); + expect(screen.queryByTestId('sloFormObjectiveTargetInput')).toHaveValue( + slo.objective.target * 100 + ); + + expect(screen.queryByTestId('sloFormNameInput')).toHaveValue(slo.name); + expect(screen.queryByTestId('sloFormDescriptionTextArea')).toHaveValue(slo.description); + }); }); describe('when submitting has completed successfully', () => { From 13f9d7ac7df0d67874ffb0978706aff01d3fe30f Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Mon, 17 Apr 2023 15:14:39 +0200 Subject: [PATCH 3/8] Add additional NODE_ENV check for RQ Dev Tools Close Button (#154987) --- .../public/application/hideable_react_query_dev_tools.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/application/hideable_react_query_dev_tools.tsx b/x-pack/plugins/observability/public/application/hideable_react_query_dev_tools.tsx index 14732d28f212a..b09bbb8656699 100644 --- a/x-pack/plugins/observability/public/application/hideable_react_query_dev_tools.tsx +++ b/x-pack/plugins/observability/public/application/hideable_react_query_dev_tools.tsx @@ -12,7 +12,7 @@ import { EuiButtonIcon } from '@elastic/eui'; export function HideableReactQueryDevTools() { const [isHidden, setIsHidden] = useState(false); - return !isHidden ? ( + return !isHidden && process.env.NODE_ENV === 'development' ? (
Date: Mon, 17 Apr 2023 15:15:15 +0200 Subject: [PATCH 4/8] Remove Has Data check from Alerts page so page is always accessible (#154851) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../observability/public/pages/alerts/alerts.tsx | 15 +-------------- .../apps/observability/pages/alerts/index.ts | 6 ------ .../apps/observability/pages/alerts/rule_stats.ts | 6 ------ 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts.tsx index 9225c2afd72f8..9defe55a3d1d4 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts.tsx @@ -28,7 +28,6 @@ import { useAlertSearchBarStateContainer, } from '../../components/shared/alert_search_bar/containers'; import { calculateTimeRangeBucketSize } from '../overview/helpers/calculate_bucket_size'; -import { getNoDataConfig } from '../../utils/no_data_config'; import { getAlertSummaryTimeRange } from '../../utils/alert_summary_widget'; import { observabilityAlertFeatureIds } from '../../config/alert_feature_ids'; import type { ObservabilityAppServices } from '../../application/types'; @@ -49,7 +48,6 @@ function InternalAlertsPage() { timefilter: { timefilter: timeFilterService }, }, }, - docLinks, http, notifications: { toasts }, triggersActionsUi: { @@ -162,25 +160,14 @@ function InternalAlertsPage() { const manageRulesHref = http.basePath.prepend('/app/observability/alerts/rules'); - // If there is any data, set hasData to true otherwise we need to wait till all the data is loaded before setting hasData to true or false; undefined indicates the data is still loading. - const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false); - if (!hasAnyData && !isAllRequestsComplete) { return ; } - const noDataConfig = getNoDataConfig({ - hasData, - basePath: http.basePath, - docsLink: docLinks.links.observability.guide, - }); - return ( {i18n.translate('xpack.observability.alertsTitle', { defaultMessage: 'Alerts' })} diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts index cc045b6720a9a..ed82ba6d57c41 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts @@ -41,12 +41,6 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); }); - describe('With no data', () => { - it('Shows the no data screen', async () => { - await observability.alerts.common.getNoDataPageOrFail(); - }); - }); - describe('Alerts table', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/simple_logs'); diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/rule_stats.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/rule_stats.ts index 15c960c16f749..10ca054265052 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/rule_stats.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/rule_stats.ts @@ -39,12 +39,6 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); }); - describe('With no data', () => { - it('Shows the no data screen', async () => { - await observability.alerts.common.getNoDataPageOrFail(); - }); - }); - // FLAKY: https://github.com/elastic/kibana/issues/124681 describe.skip('Stat counters', () => { beforeEach(async () => { From f3e5759a737705b6dbd5aca0c2cc8f28d1ea99c5 Mon Sep 17 00:00:00 2001 From: Klim Markelov Date: Mon, 17 Apr 2023 15:28:13 +0200 Subject: [PATCH 5/8] [Behavioral Analytics] Change events state check (#154744) ## https://github.com/elastic/enterprise-search-team/issues/4270 ### Description In order to improve user experience, it's required to introduce a Callout when a datastream doesn't have any events. This PR is dedicated to: 1. Introducing the `EuiCallout` element that redirects to the Integration page, when clicking on "Learn how" button 2. Changing the way how we check if datastream has any events ### Screenshots Screenshot 2023-04-11 at 18 19 23 --- .../common/types/analytics.ts | 4 +- ..._analytics_events_exist_api_logic.test.ts} | 8 +- .../check_analytics_events_exist_api_logic.ts | 31 ++++++++ .../check_analytics_events_index_api_logic.ts | 31 -------- ...tics_collection_no_events_callout.test.tsx | 61 +++++++++++++++ ...analytics_collection_no_events_callout.tsx | 78 +++++++++++++++++++ ...ollection_no_events_callout_logic.test.ts} | 24 +++--- ...ics_collection_no_events_callout_logic.tsx | 47 +++++++++++ .../analytics_collection_overview.test.tsx | 17 ++++ .../analytics_collection_overview.tsx | 6 +- .../analytics_events_index_exists_logic.tsx | 52 ------------- ...test.ts => analytics_events_exist.test.ts} | 16 ++-- ...ex_exists.ts => analytics_events_exist.ts} | 8 +- .../routes/enterprise_search/analytics.ts | 6 +- 14 files changed, 270 insertions(+), 119 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/analytics/api/{check_analytics_events_index/check_analytics_events_index_api_logic.test.ts => check_analytics_events/check_analytics_events_exist_api_logic.test.ts} (75%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx rename x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/{analytics_events_index_exists_logic.test.ts => analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.test.ts} (50%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.tsx rename x-pack/plugins/enterprise_search/server/lib/analytics/{analytics_events_index_exists.test.ts => analytics_events_exist.test.ts} (60%) rename x-pack/plugins/enterprise_search/server/lib/analytics/{analytics_events_index_exists.ts => analytics_events_exist.ts} (75%) diff --git a/x-pack/plugins/enterprise_search/common/types/analytics.ts b/x-pack/plugins/enterprise_search/common/types/analytics.ts index ca6629f214615..d50b4dc6e98c0 100644 --- a/x-pack/plugins/enterprise_search/common/types/analytics.ts +++ b/x-pack/plugins/enterprise_search/common/types/analytics.ts @@ -10,8 +10,8 @@ export interface AnalyticsCollection { name: string; } -export interface AnalyticsEventsIndexExists { - exists: boolean; +export interface AnalyticsEventsExist { + exist: boolean; } export interface AnalyticsCollectionDataViewId { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.test.ts similarity index 75% rename from x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.test.ts rename to x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.test.ts index b7173f20be3be..091b715744240 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.test.ts @@ -9,9 +9,9 @@ import { mockHttpValues } from '../../../__mocks__/kea_logic'; import { nextTick } from '@kbn/test-jest-helpers'; -import { checkAnalyticsEventsIndexExists } from './check_analytics_events_index_api_logic'; +import { checkAnalyticsEventsExist } from './check_analytics_events_exist_api_logic'; -describe('FetchAnalyticsCollectionApiLogic', () => { +describe('AnalyticsEventsExistApiLogic', () => { const { http } = mockHttpValues; beforeEach(() => { jest.clearAllMocks(); @@ -22,10 +22,10 @@ describe('FetchAnalyticsCollectionApiLogic', () => { const promise = Promise.resolve({ exists: true }); const indexName = 'eventsIndex'; http.get.mockReturnValue(promise); - const result = checkAnalyticsEventsIndexExists({ indexName }); + const result = checkAnalyticsEventsExist({ indexName }); await nextTick(); expect(http.get).toHaveBeenCalledWith( - `/internal/enterprise_search/analytics/events/${indexName}/exists` + `/internal/enterprise_search/analytics/collection/${indexName}/events/exist` ); await expect(result).resolves.toEqual({ exists: true }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.ts new file mode 100644 index 0000000000000..083297f571a9e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events/check_analytics_events_exist_api_logic.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 { AnalyticsEventsExist } from '../../../../../common/types/analytics'; +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface AnalyticsEventsExistApiLogicArgs { + indexName: string; +} + +export type AnalyticsEventsExistApiLogicResponse = AnalyticsEventsExist; + +export const checkAnalyticsEventsExist = async ({ + indexName, +}: AnalyticsEventsExistApiLogicArgs): Promise => { + const { http } = HttpLogic.values; + const route = `/internal/enterprise_search/analytics/collection/${indexName}/events/exist`; + const response = await http.get(route); + + return response; +}; + +export const AnalyticsEventsExistAPILogic = createApiLogic( + ['analytics', 'analytics_events_exist_api_logic'], + checkAnalyticsEventsExist +); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.ts deleted file mode 100644 index 37a0d1a796179..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/api/check_analytics_events_index/check_analytics_events_index_api_logic.ts +++ /dev/null @@ -1,31 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AnalyticsEventsIndexExists } from '../../../../../common/types/analytics'; -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; -import { HttpLogic } from '../../../shared/http'; - -export interface AnalyticsEventsIndexExistsApiLogicArgs { - indexName: string; -} - -export type AnalyticsEventsIndexExistsApiLogicResponse = AnalyticsEventsIndexExists; - -export const checkAnalyticsEventsIndexExists = async ({ - indexName, -}: AnalyticsEventsIndexExistsApiLogicArgs): Promise => { - const { http } = HttpLogic.values; - const route = `/internal/enterprise_search/analytics/events/${indexName}/exists`; - const response = await http.get(route); - - return response; -}; - -export const AnalyticsEventsIndexExistsAPILogic = createApiLogic( - ['analytics', 'analytics_events_index_exists_api_logic'], - checkAnalyticsEventsIndexExists -); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.test.tsx new file mode 100644 index 0000000000000..d0db79df05484 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { AnalyticsCollection } from '../../../../../../common/types/analytics'; + +import { AnalyticsCollectionNoEventsCallout } from './analytics_collection_no_events_callout'; + +const mockValues = { + analyticsCollection: { + events_datastream: 'analytics-events-example', + name: 'Analytics-Collection-1', + } as AnalyticsCollection, + hasEvents: true, +}; + +const mockActions = { + fetchAnalyticsCollection: jest.fn(), + fetchAnalyticsCollectionDataViewId: jest.fn(), + analyticsEventsExist: jest.fn(), + setTimeRange: jest.fn(), +}; + +describe('AnalyticsCollectionNoEventsCallout', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders no events Callout when the collection has no events', () => { + setMockValues({ ...mockValues, hasEvents: false }); + setMockActions(mockActions); + + const wrapper = shallow( + + ); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); + + it('does not render events Callout when the collection has events', () => { + setMockValues(mockValues); + setMockActions(mockActions); + + const wrapper = shallow( + + ); + + expect(wrapper.find('EuiCallOut')).toHaveLength(0); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx new file mode 100644 index 0000000000000..7e5ed567bb846 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useValues, useActions } from 'kea'; + +import { EuiButton, EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { AnalyticsCollection } from '../../../../../../common/types/analytics'; + +import { generateEncodedPath } from '../../../../shared/encode_path_params'; + +import { KibanaLogic } from '../../../../shared/kibana'; +import { COLLECTION_INTEGRATE_PATH } from '../../../routes'; + +import { AnalyticsCollectionNoEventsCalloutLogic } from './analytics_collection_no_events_callout_logic'; + +interface AnalyticsCollectionNoEventsCalloutProps { + analyticsCollection: AnalyticsCollection; +} + +export const AnalyticsCollectionNoEventsCallout: React.FC< + AnalyticsCollectionNoEventsCalloutProps +> = ({ analyticsCollection }) => { + const { navigateToUrl } = useValues(KibanaLogic); + const { analyticsEventsExist } = useActions(AnalyticsCollectionNoEventsCalloutLogic); + const { hasEvents } = useValues(AnalyticsCollectionNoEventsCalloutLogic); + + useEffect(() => { + analyticsEventsExist(analyticsCollection.name); + }, []); + + return hasEvents ? null : ( + + + {i18n.translate( + 'xpack.enterpriseSearch.analytics.collectionsView.noEventsCallout.description', + { + defaultMessage: + 'Start receiving metric data in this Collection by installing our tracker in your search application.', + } + )} + + + + navigateToUrl( + generateEncodedPath(COLLECTION_INTEGRATE_PATH, { + name: analyticsCollection.name, + }) + ) + } + > + {i18n.translate('xpack.enterpriseSearch.analytics.collectionsView.noEventsCallout.button', { + defaultMessage: 'Learn how', + })} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.test.ts similarity index 50% rename from x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.test.ts rename to x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.test.ts index e93e5e5f7108c..63098e2bd574a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.test.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { LogicMounter } from '../../../__mocks__/kea_logic'; +import { LogicMounter } from '../../../../__mocks__/kea_logic'; -import { Status } from '../../../../../common/types/api'; +import { Status } from '../../../../../../common/types/api'; -import { AnalyticsEventsIndexExistsLogic } from './analytics_events_index_exists_logic'; +import { AnalyticsCollectionNoEventsCalloutLogic } from './analytics_collection_no_events_callout_logic'; -describe('analyticsEventsIndexExistsLogic', () => { - const { mount } = new LogicMounter(AnalyticsEventsIndexExistsLogic); +describe('analyticsEventsExistLogic', () => { + const { mount } = new LogicMounter(AnalyticsCollectionNoEventsCalloutLogic); const indexName = true; beforeEach(() => { @@ -23,25 +23,23 @@ describe('analyticsEventsIndexExistsLogic', () => { const DEFAULT_VALUES = { data: undefined, - isLoading: true, - isPresent: false, + hasEvents: false, status: Status.IDLE, }; it('has expected default values', () => { - expect(AnalyticsEventsIndexExistsLogic.values).toEqual(DEFAULT_VALUES); + expect(AnalyticsCollectionNoEventsCalloutLogic.values).toEqual(DEFAULT_VALUES); }); describe('selectors', () => { it('updates when apiSuccess listener triggered', () => { - AnalyticsEventsIndexExistsLogic.actions.apiSuccess({ exists: indexName }); + AnalyticsCollectionNoEventsCalloutLogic.actions.apiSuccess({ exist: indexName }); - expect(AnalyticsEventsIndexExistsLogic.values).toEqual({ + expect(AnalyticsCollectionNoEventsCalloutLogic.values).toEqual({ ...DEFAULT_VALUES, - isLoading: false, - isPresent: true, + hasEvents: true, status: Status.SUCCESS, - data: { exists: indexName }, + data: { exist: indexName }, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx new file mode 100644 index 0000000000000..c63ddc97cc536 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.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 { kea, MakeLogicType } from 'kea'; + +import { Status } from '../../../../../../common/types/api'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { + AnalyticsEventsExistAPILogic, + AnalyticsEventsExistApiLogicResponse, +} from '../../../api/check_analytics_events/check_analytics_events_exist_api_logic'; + +export interface AnalyticsCollectionNoEventsCalloutActions { + apiSuccess: Actions<{}, AnalyticsEventsExistApiLogicResponse>['apiSuccess']; + analyticsEventsExist(indexName: string): { indexName: string }; + makeRequest: Actions<{}, AnalyticsEventsExistApiLogicResponse>['makeRequest']; +} +export interface AnalyticsCollectionNoEventsCalloutValues { + hasEvents: boolean; + status: Status; + data: typeof AnalyticsEventsExistAPILogic.values.data; +} + +export const AnalyticsCollectionNoEventsCalloutLogic = kea< + MakeLogicType +>({ + actions: { + analyticsEventsExist: (indexName) => ({ indexName }), + }, + connect: { + actions: [AnalyticsEventsExistAPILogic, ['makeRequest', 'apiSuccess', 'apiError']], + values: [AnalyticsEventsExistAPILogic, ['status', 'data']], + }, + listeners: ({ actions }) => ({ + analyticsEventsExist: ({ indexName }) => { + actions.makeRequest({ indexName }); + }, + }), + path: ['enterprise_search', 'analytics', 'events_exist'], + selectors: ({ selectors }) => ({ + hasEvents: [() => [selectors.data], (data) => data?.exist === true], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx index 65c3b2c845d24..b9e54854978fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx @@ -18,6 +18,8 @@ import { FilterBy } from '../../../utils/get_formula_by_filter'; import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template'; +import { AnalyticsCollectionNoEventsCallout } from '../analytics_collection_no_events_callout/analytics_collection_no_events_callout'; + import { AnalyticsCollectionChartWithLens } from './analytics_collection_chart'; import { AnalyticsCollectionViewMetricWithLens } from './analytics_collection_metric'; @@ -38,6 +40,7 @@ const mockValues = { const mockActions = { fetchAnalyticsCollection: jest.fn(), fetchAnalyticsCollectionDataViewId: jest.fn(), + analyticsEventsExist: jest.fn(), setTimeRange: jest.fn(), }; @@ -47,6 +50,9 @@ describe('AnalyticsOverView', () => { }); it('renders with Data', async () => { + setMockValues(mockValues); + setMockActions(mockActions); + const wrapper = shallow( ); @@ -121,4 +127,15 @@ describe('AnalyticsOverView', () => { FilterBy.NoResults ); }); + + it('renders no events AnalyticsCollectionNoEventsCallout with collection', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(AnalyticsCollectionNoEventsCallout)).toHaveLength(1); + expect(wrapper?.find(AnalyticsCollectionNoEventsCallout).props()).toEqual({ + analyticsCollection: mockValues.analyticsCollection, + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx index a13d66053ea63..04a8ab8f675c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -20,6 +20,7 @@ import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_templat import { AnalyticsCollectionExploreTable } from '../analytics_collection_explore_table/analytics_collection_explore_table'; +import { AnalyticsCollectionNoEventsCallout } from '../analytics_collection_no_events_callout/analytics_collection_no_events_callout'; import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar'; import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic'; @@ -90,6 +91,9 @@ export const AnalyticsCollectionOverview: React.FC], }} > + + + {filters.map(({ name, id }) => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.tsx deleted file mode 100644 index f9449da5172c8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_events_index_exists_logic.tsx +++ /dev/null @@ -1,52 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { kea, MakeLogicType } from 'kea'; - -import { Status } from '../../../../../common/types/api'; -import { Actions } from '../../../shared/api_logic/create_api_logic'; -import { - AnalyticsEventsIndexExistsAPILogic, - AnalyticsEventsIndexExistsApiLogicResponse, -} from '../../api/check_analytics_events_index/check_analytics_events_index_api_logic'; - -export interface AnalyticsEventsIndexExistsActions { - apiSuccess: Actions<{}, AnalyticsEventsIndexExistsApiLogicResponse>['apiSuccess']; - analyticsEventsIndexExists(indexName: string): { indexName: string }; - makeRequest: Actions<{}, AnalyticsEventsIndexExistsApiLogicResponse>['makeRequest']; -} -export interface AnalyticsEventsIndexExistsValues { - isLoading: boolean; - isPresent: boolean; - status: Status; - data: typeof AnalyticsEventsIndexExistsAPILogic.values.data; -} - -export const AnalyticsEventsIndexExistsLogic = kea< - MakeLogicType ->({ - actions: { - analyticsEventsIndexExists: (indexName) => ({ indexName }), - }, - connect: { - actions: [AnalyticsEventsIndexExistsAPILogic, ['makeRequest', 'apiSuccess', 'apiError']], - values: [AnalyticsEventsIndexExistsAPILogic, ['status', 'data']], - }, - listeners: ({ actions }) => ({ - analyticsEventsIndexExists: ({ indexName }) => { - actions.makeRequest({ indexName }); - }, - }), - path: ['enterprise_search', 'analytics', 'events_index'], - selectors: ({ selectors }) => ({ - isLoading: [ - () => [selectors.status], - (status) => [Status.LOADING, Status.IDLE].includes(status), - ], - isPresent: [() => [selectors.data], (data) => data?.exists === true], - }), -}); diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_index_exists.test.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_exist.test.ts similarity index 60% rename from x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_index_exists.test.ts rename to x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_exist.test.ts index a851f21eaa151..e77cf142b676c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_index_exists.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_exist.test.ts @@ -7,14 +7,12 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; -import { analyticsEventsIndexExists } from './analytics_events_index_exists'; +import { analyticsEventsExist } from './analytics_events_exist'; describe('analytics collection events exists function', () => { const mockClient = { asCurrentUser: { - indices: { - getDataStream: jest.fn(), - }, + count: jest.fn(), }, }; @@ -24,14 +22,14 @@ describe('analytics collection events exists function', () => { describe('checking if analytics events index exists', () => { it('should call exists endpoint', async () => { - mockClient.asCurrentUser.indices.getDataStream.mockImplementationOnce(() => ({ - data_streams: [{ name: 'example' }], + mockClient.asCurrentUser.count.mockImplementationOnce(() => ({ + count: 1, })); await expect( - analyticsEventsIndexExists(mockClient as unknown as IScopedClusterClient, 'example') + analyticsEventsExist(mockClient as unknown as IScopedClusterClient, 'example') ).resolves.toEqual(true); - expect(mockClient.asCurrentUser.indices.getDataStream).toHaveBeenCalledWith({ - name: 'example', + expect(mockClient.asCurrentUser.count).toHaveBeenCalledWith({ + index: 'example', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_index_exists.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_exist.ts similarity index 75% rename from x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_index_exists.ts rename to x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_exist.ts index d9549c42494f6..3951fef8c9a1e 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_index_exists.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/analytics_events_exist.ts @@ -9,15 +9,15 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { isIndexNotFoundException } from '../../utils/identify_exceptions'; -export const analyticsEventsIndexExists = async ( +export const analyticsEventsExist = async ( client: IScopedClusterClient, datastreamName: string ): Promise => { try { - const response = await client.asCurrentUser.indices.getDataStream({ - name: datastreamName, + const response = await client.asCurrentUser.count({ + index: datastreamName, }); - return response.data_streams.length > 0; + return response.count > 0; } catch (error) { if (isIndexNotFoundException(error)) { return false; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts index 31d1f4cc123c9..4bc99895a6d44 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { ErrorCode } from '../../../common/types/error_codes'; import { addAnalyticsCollection } from '../../lib/analytics/add_analytics_collection'; -import { analyticsEventsIndexExists } from '../../lib/analytics/analytics_events_index_exists'; +import { analyticsEventsExist } from '../../lib/analytics/analytics_events_exist'; import { createApiKey } from '../../lib/analytics/create_api_key'; import { deleteAnalyticsCollectionById } from '../../lib/analytics/delete_analytics_collection'; import { fetchAnalyticsCollections } from '../../lib/analytics/fetch_analytics_collection'; @@ -181,7 +181,7 @@ export function registerAnalyticsRoutes({ router.get( { - path: '/internal/enterprise_search/analytics/events/{name}/exists', + path: '/internal/enterprise_search/analytics/collection/{name}/events/exist', validate: { params: schema.object({ name: schema.string(), @@ -191,7 +191,7 @@ export function registerAnalyticsRoutes({ elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const eventsIndexExists = await analyticsEventsIndexExists(client, request.params.name); + const eventsIndexExists = await analyticsEventsExist(client, request.params.name); if (!eventsIndexExists) { return response.ok({ body: { exists: false } }); From d213107c15ac6df7c2997c8022402c783517e559 Mon Sep 17 00:00:00 2001 From: Sean Story Date: Mon, 17 Apr 2023 08:43:46 -0500 Subject: [PATCH 6/8] Have createPipeline pass along fieldMappings (#154951) ## Summary previously createPipeline wasn't passing along fieldMappings, which was leading to errors like: screenshot ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../enterprise_search/common/types/pipelines.ts | 3 +++ .../pipelines/create_ml_inference_pipeline.ts | 10 +++++++--- .../ml_inference/ml_inference_logic.test.ts | 16 ++++++++++++++-- .../pipelines/ml_inference/ml_inference_logic.ts | 5 +++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/types/pipelines.ts b/x-pack/plugins/enterprise_search/common/types/pipelines.ts index 3e652d97c6f4f..ea86183ce9ea8 100644 --- a/x-pack/plugins/enterprise_search/common/types/pipelines.ts +++ b/x-pack/plugins/enterprise_search/common/types/pipelines.ts @@ -7,6 +7,8 @@ import { IngestInferenceConfig, IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; +import { FieldMapping } from '../ml_inference_pipeline'; + export interface InferencePipeline { modelId: string | undefined; modelState: TrainedModelState; @@ -81,6 +83,7 @@ export interface CreateMlInferencePipelineParameters { } export interface CreateMLInferencePipelineDefinition { + field_mappings: FieldMapping[]; pipeline_definition: MlInferencePipeline; pipeline_name: string; } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts index cc224093cc4e8..5161ec751bd77 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { FieldMapping } from '../../../../../common/ml_inference_pipeline'; + import { CreateMlInferencePipelineParameters, CreateMLInferencePipelineDefinition, @@ -23,9 +25,10 @@ interface CreateMlInferencePipelineApiLogicArgsWithPipelineParameters { } interface CreateMlInferencePipelineApiLogicArgsWithPipelineDefinition { + fieldMappings: FieldMapping[]; indexName: string; - pipelineName: string; pipelineDefinition: MlInferencePipeline; + pipelineName: string; } export type CreateMlInferencePipelineApiLogicArgs = @@ -64,14 +67,15 @@ export const createMlInferencePipeline = async ( destination_field: args.destinationField, inference_config: args.inferenceConfig, model_id: args.modelId, - pipeline_name: args.pipelineName, pipeline_definition: undefined, + pipeline_name: args.pipelineName, source_field: args.sourceField, }; } else if (isArgsWithPipelineDefinition(args)) { params = { - pipeline_name: args.pipelineName, + field_mappings: args.fieldMappings, pipeline_definition: args.pipelineDefinition, + pipeline_name: args.pipelineName, }; } return await HttpLogic.values.http.post(route, { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts index 531678629aeb7..1c38c99bd5c93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts @@ -531,10 +531,16 @@ describe('MlInferenceLogic', () => { MLModelsApiLogic.actions.apiSuccess([textExpansionModel]); MLInferenceLogic.actions.setInferencePipelineConfiguration({ - destinationField: 'my-dest-field', + destinationField: mockModelConfiguration.configuration.destinationField, + fieldMappings: [ + { + sourceField: 'source', + targetField: 'ml.inference.dest', + }, + ], modelID: textExpansionModel.model_id, pipelineName: mockModelConfiguration.configuration.pipelineName, - sourceField: 'my-field', + sourceField: mockModelConfiguration.configuration.sourceField, }); MLInferenceLogic.actions.createPipeline(); @@ -542,6 +548,12 @@ describe('MlInferenceLogic', () => { indexName: mockModelConfiguration.indexName, pipelineName: mockModelConfiguration.configuration.pipelineName, pipelineDefinition: expect.any(Object), // Generation logic is tested elsewhere + fieldMappings: [ + { + sourceField: 'source', + targetField: 'ml.inference.dest', + }, + ], }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts index 996ae0e6e8708..98bcaa6671495 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -257,11 +257,12 @@ export const MLInferenceLogic = kea< mlInferencePipeline, // Full pipeline definition } = values; actions.makeCreatePipelineRequest( - isTextExpansionModelSelected && mlInferencePipeline + isTextExpansionModelSelected && mlInferencePipeline && configuration.fieldMappings ? { indexName, - pipelineName: configuration.pipelineName, + fieldMappings: configuration.fieldMappings, pipelineDefinition: mlInferencePipeline, + pipelineName: configuration.pipelineName, } : { destinationField: From eb6bfd8476c77b31361b1c1a914ec047e53799d0 Mon Sep 17 00:00:00 2001 From: Lola Date: Mon, 17 Apr 2023 10:17:47 -0400 Subject: [PATCH 7/8] [Cloud Posture] Vul mgmt flyout details panel (#154873) ## Summary Summarize your PR. If it involves visual changes include a screenshot or gif. Screen Shot 2023-04-12 at 5 04 30 PM Screen Shot 2023-04-12 at 5 30 55 PM Screen Shot 2023-04-12 at 5 31 09 PM Flyou details Feature includes: Results are fetched from the logs-cloud_security_posture.vulnerabilities-default index. A Flyout opens when clicking the Expand Icon in the Vulnerabilities table. The Flyout has two tabs: Overview and JSON. The Overview tab consists of four sections: CVSS, Data source, Publish date, Description, Fixes, and Vulnerability Scores TODO Add pagination in follow up pr Add unit tests --- .../components/vulnerability_badges.tsx | 33 +-- .../public/pages/vulnerabilities/types.ts | 98 ++++--- .../public/pages/vulnerabilities/utils.ts | 27 ++ .../pages/vulnerabilities/vulnerabilities.tsx | 143 ++++++---- .../vulnerability_finding_flyout.tsx | 149 ++++++++++ .../vulnerability_json_tab.tsx | 32 +++ .../vulnerability_overview_tab.tsx | 260 ++++++++++++++++++ 7 files changed, 616 insertions(+), 126 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx create mode 100644 x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_json_tab.tsx create mode 100644 x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx diff --git a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx index 713b29d06b573..61d8f4b849550 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBadge, EuiIcon, EuiTextColor, useEuiFontSize } from '@elastic/eui'; +import { EuiBadge, EuiIcon, EuiTextColor } from '@elastic/eui'; import React from 'react'; import { css } from '@emotion/react'; import { float } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -21,10 +21,6 @@ interface SeverityStatusBadgeProps { score: float; } -interface ExploitsStatusBadgeProps { - totalExploits: number; -} - export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => { const color = getCvsScoreColor(score); const versionDisplay = version ? `v${version.split('.')[0]}` : null; @@ -60,28 +56,15 @@ export const SeverityStatusBadge = ({ score, status }: SeverityStatusBadgeProps) const color = getCvsScoreColor(score); return ( - <> - - {status} - - ); -}; - -export const ExploitsStatusBadge = ({ totalExploits }: ExploitsStatusBadgeProps) => { - const xxsFontSize = useEuiFontSize('xxs').fontSize; - - return ( - - {totalExploits} - + + {status} +
); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts index d7810fde2a1fb..8f01271677321 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts @@ -20,48 +20,7 @@ export interface VulnerabilityRecord { sequence: number; outcome: string; }; - vulnerability: { - score: { - version: string; - impact: number; - base: number; - }; - cwe: string[]; - id: string; - title: string; - reference: string; - severity: string; - cvss: { - nvd: { - V3Vector: string; - V3Score: number; - }; - redhat?: { - V3Vector: string; - V3Score: number; - }; - ghsa?: { - V3Vector: string; - V3Score: number; - }; - }; - data_source: { - ID: string; - Name: string; - URL: string; - }; - enumeration: string; - description: string; - classification: string; - scanner: { - vendor: string; - }; - package: { - version: string; - name: string; - fixed_version: string; - }; - }; + vulnerability: Vulnerability; ecs: { version: string; }; @@ -116,3 +75,58 @@ export interface VulnerabilityRecord { commit_time: string; }; } + +export interface Vulnerability { + published_at: string; + score: { + version: string; + impact: number; + base: number; + }; + cwe: string[]; + id: string; + title: string; + reference: string; + severity: string; + cvss: { + nvd: VectorScoreBase; + redhat?: VectorScoreBase; + ghsa?: VectorScoreBase; + }; + data_source: { + ID: string; + Name: string; + URL: string; + }; + enumeration: string; + description: string; + classification: string; + scanner: { + vendor: string; + }; + package: { + version: string; + name: string; + fixed_version: string; + }; +} + +export interface VectorScoreBase { + V3Score?: number; + V3Vector?: string; + V2Score?: number; + V2Vector?: string; +} + +export type Vendor = 'NVD' | 'Red Hat' | 'GHSA'; + +export interface CVSScoreProps { + vectorBaseScore: VectorScoreBase; + vendor: string; +} + +export interface Vector { + version: string; + vector: string; + score: number | undefined; +} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts index ff2171aa76013..4e9c346e3969a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts @@ -7,6 +7,7 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { VectorScoreBase, Vector } from './types'; export const vulnerabilitiesColumns = { actions: 'actions', @@ -85,3 +86,29 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => { }, ]; }; + +export const getVectorScoreList = (vectorBaseScore: VectorScoreBase) => { + const result: Vector[] = []; + const v2Vector = vectorBaseScore?.V2Vector; + const v2Score = vectorBaseScore?.V2Score; + const v3Vector = vectorBaseScore?.V3Vector; + const v3Score = vectorBaseScore?.V3Score; + + if (v2Vector) { + result.push({ + version: '2.0', + vector: v2Vector, + score: v2Score, + }); + } + + if (v3Vector) { + result.push({ + version: '2.0', + vector: v3Vector, + score: v3Score, + }); + } + + return result; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx index 174939a5e0f99..c63d45d9592d6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx @@ -17,7 +17,7 @@ import { import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataView } from '@kbn/data-views-plugin/common'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY } from '../../common/constants'; import { useCloudPostureTable } from '../../common/hooks/use_cloud_posture_table'; @@ -29,6 +29,7 @@ import { ErrorCallout } from '../configurations/layout/error_callout'; import { FindingsSearchBar } from '../configurations/layout/findings_search_bar'; import { useFilteredDataView } from '../../common/api/use_filtered_data_view'; import { CVSScoreBadge, SeverityStatusBadge } from '../../components/vulnerability_badges'; +import { VulnerabilityFindingFlyout } from './vulnerabilities_finding_flyout/vulnerability_finding_flyout'; import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states'; import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api'; @@ -79,6 +80,21 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { enabled: !queryError, }); + const [isVulnerabilityDetailFlyoutVisible, setIsVulnerabilityDetailFlyoutVisible] = + useState(false); + + const [vulnerability, setVulnerability] = useState(); + + const showFlyout = (vulnerabilityRecord: VulnerabilityRecord) => { + setIsVulnerabilityDetailFlyoutVisible(true); + setVulnerability(vulnerabilityRecord); + }; + + const hideFlyout = () => { + setIsVulnerabilityDetailFlyoutVisible(false); + setVulnerability(undefined); + }; + const renderCellValue = useMemo(() => { return ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { const vulnerabilityRow = data?.page[rowIndex] as VulnerabilityRecord; @@ -92,7 +108,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { iconType="expand" aria-label="View" onClick={() => { - alert(`Flyout id ${vulnerabilityRow.vulnerability.id}`); + showFlyout(vulnerabilityRow); }} /> ); @@ -181,64 +197,73 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { } /> ) : ( - id), - setVisibleColumns: () => {}, - }} - rowCount={data?.total} - toolbarVisibility={{ - showColumnSelector: false, - showDisplaySelector: false, - showKeyboardShortcuts: false, - additionalControls: { - left: { - prepend: ( - - {i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', { - defaultMessage: - '{total, plural, one {# Vulnerability} other {# Vulnerabilities}}', - values: { total: data?.total }, - })} - - ), + <> + id), + setVisibleColumns: () => {}, + }} + rowCount={data?.total} + toolbarVisibility={{ + showColumnSelector: false, + showDisplaySelector: false, + showKeyboardShortcuts: false, + additionalControls: { + left: { + prepend: ( + + {i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', { + defaultMessage: + '{total, plural, one {# Vulnerability} other {# Vulnerabilities}}', + values: { total: data?.total }, + })} + + ), + }, }, - }, - }} - gridStyle={{ - border: 'horizontal', - cellPadding: 'l', - stripes: false, - rowHover: 'none', - header: 'underline', - }} - renderCellValue={renderCellValue} - inMemory={{ level: 'sorting' }} - sorting={{ columns: sort, onSort }} - pagination={{ - pageIndex, - pageSize, - pageSizeOptions: [10, 25, 100], - onChangeItemsPerPage, - onChangePage, - }} - /> + }} + gridStyle={{ + border: 'horizontal', + cellPadding: 'l', + stripes: false, + rowHover: 'none', + header: 'underline', + }} + renderCellValue={renderCellValue} + inMemory={{ level: 'sorting' }} + sorting={{ columns: sort, onSort }} + pagination={{ + pageIndex, + pageSize, + pageSizeOptions: [10, 25, 100], + onChangeItemsPerPage, + onChangePage, + }} + /> + {/* Todo: Add Pagination */} + {isVulnerabilityDetailFlyoutVisible && !!vulnerability && ( + + )} + )} ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx new file mode 100644 index 0000000000000..b29239f2dc275 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx @@ -0,0 +1,149 @@ +/* + * 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, { useMemo, useState } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiLink, + EuiTab, + EuiTabs, + EuiTitle, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/react'; +import { VulnerabilityOverviewTab } from './vulnerability_overview_tab'; +import { VulnerabilityJsonTab } from './vulnerability_json_tab'; +import { SeverityStatusBadge } from '../../../components/vulnerability_badges'; +import { VulnerabilityRecord } from '../types'; + +const overviewTabId = 'overview'; +const jsonTabId = 'json'; + +export const VulnerabilityFindingFlyout = ({ + closeFlyout, + vulnerabilityRecord, +}: { + closeFlyout: () => void; + vulnerabilityRecord: VulnerabilityRecord; +}) => { + const [selectedTabId, setSelectedTabId] = useState(overviewTabId); + const vulnerability = vulnerabilityRecord?.vulnerability; + const resourceName = vulnerabilityRecord?.resource?.name; + + const tabs = useMemo( + () => [ + { + id: overviewTabId, + name: ( + + ), + content: , + }, + { + id: jsonTabId, + name: ( + + ), + content: , + }, + ], + [vulnerability, vulnerabilityRecord] + ); + + const onSelectedTabChanged = (id: string) => setSelectedTabId(id); + + const renderTabs = () => + tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + key={index} + > + {tab.name} + + )); + + const selectedTabContent = useMemo( + () => tabs.find((obj) => obj.id === selectedTabId)?.content, + [selectedTabId, tabs] + ); + const nvdDomain = 'https://nvd'; + const nvdWebsite = `${nvdDomain}.nist.gov/vuln/detail/${vulnerabilityRecord?.vulnerability?.id}`; + + const vulnerabilityReference = vulnerability?.reference.startsWith(nvdDomain) + ? vulnerability?.reference + : nvdWebsite; + + return ( + + + + + + + + + + + + {vulnerability?.id} + + + + +

+ {`${resourceName} | ${vulnerability?.package?.name} ${vulnerability?.package?.version}`} +

+
+
+
+ + {renderTabs()} + +
+
+ {selectedTabContent} + {/* Todo: Add Pagination */} +
+ ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_json_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_json_tab.tsx new file mode 100644 index 0000000000000..99f0da1eb4454 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_json_tab.tsx @@ -0,0 +1,32 @@ +/* + * 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 { CodeEditor } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import { XJsonLang } from '@kbn/monaco'; +import { VulnerabilityRecord } from '../types'; +interface VulnerabilityJsonTabProps { + vulnerabilityRecord: VulnerabilityRecord; +} +export const VulnerabilityJsonTab = ({ vulnerabilityRecord }: VulnerabilityJsonTabProps) => { + const offsetHeight = 188; + return ( +
+ +
+ ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx new file mode 100644 index 0000000000000..9ecfbdd5eb917 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx @@ -0,0 +1,260 @@ +/* + * 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, EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import moment from 'moment'; +import React from 'react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import { CVSScoreBadge } from '../../../components/vulnerability_badges'; +import { CVSScoreProps, VectorScoreBase, Vendor, Vulnerability } from '../types'; +import { getVectorScoreList } from '../utils'; + +const cvssVendors: Record = { + nvd: 'NVD', + redhat: 'Red Hat', + ghsa: 'GHSA', +}; + +interface VulnerabilityTabProps { + vulnerability: Vulnerability; +} + +const CVSScore = ({ vectorBaseScore, vendor }: CVSScoreProps) => { + const vendorName = + cvssVendors[vendor] ?? + i18n.translate( + 'xpack.csp.vulnerabilities.vulnerabilityOverviewTab.cvsScore.unknownVendorName', + { + defaultMessage: 'Unknown vendor', + } + ); + + const vectorScores = getVectorScoreList(vectorBaseScore); + + return ( + + + {vendorName} + + + {vectorScores.length > 0 && + vectorScores.map((vectorScore) => )} + + + ); +}; + +const VectorScore = ({ + vectorScore, +}: { + vectorScore: { + vector: string; + score: number | undefined; + version: string; + }; +}) => { + const { score, vector, version } = vectorScore; + return ( + <> + + + + {vector}{' '} + + + + + {score && } + + + + ); +}; + +const VulnerabilityOverviewTiles = ({ vulnerability }: VulnerabilityTabProps) => { + const tileStyle = css` + padding: ${euiThemeVars.euiFontSizeM}; + background: ${euiThemeVars.euiColorLightestShade}; + border-radius: 6px; + height: 74px; + `; + const tileTitleTextStyle = css` + line-height: 20px; + margin-bottom: 6px; + `; + + const date = moment(vulnerability?.published_at).format('LL').toString(); + + return ( + + + + + +
+ +
+
+ + + + + + {vulnerability?.data_source?.ID} + + + + + + + + + + + +
+ ); +}; + +export const VulnerabilityOverviewTab = ({ vulnerability }: VulnerabilityTabProps) => { + const emptyFixesMessageState = i18n.translate( + 'xpack.csp.vulnerabilities.vulnerabilityOverviewTab.emptyFixesMessage', + { + defaultMessage: 'No available fixes yet.', + } + ); + + const fixesDisplayText = vulnerability?.package?.fixed_version + ? `${vulnerability?.package?.name} ${vulnerability?.package?.fixed_version}` + : emptyFixesMessageState; + + const cvssScores: JSX.Element[] = Object.entries(vulnerability?.cvss).map( + ([vendor, vectorScoreBase]: [string, VectorScoreBase]) => { + return ( + + + + ); + } + ); + + const horizontalStyle = css` + margin-block: 12px; + `; + + const flyoutSubheadingStyle = css` + font-size: ${euiThemeVars.euiFontSizeM}; + line-height: 24px; + margin-bottom: ${euiThemeVars.euiSizeS}; + font-weight: 600; + `; + + return ( + + + + + + + + +

+ +

+ + + +
+ + + + +

+ +

+ {fixesDisplayText} +
+ + + + {cvssScores?.length > 0 && ( + +

+ +

+ + {cvssScores} + +
+ )} +
+ ); +}; From 4edb583c189b705e00dc388428fc1669ae0882df Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 17 Apr 2023 16:25:58 +0200 Subject: [PATCH 8/8] [Content Management] Remove dep from content_management -> saved_object_finder (#155013) ## Summary Follow up https://github.com/elastic/kibana/pull/154819, @mattkime pointed out a problem that cm can't depend on saved_object_finder https://github.com/elastic/kibana/pull/154819/files#diff-635bc20df585b656afebba3ebf338ff997e735df933f704cc5f253a74b3503ddR17 --- .github/CODEOWNERS | 1 + package.json | 1 + packages/kbn-saved-objects-settings/README.md | 3 +++ packages/kbn-saved-objects-settings/index.ts | 10 ++++++++++ .../kbn-saved-objects-settings/jest.config.js | 13 +++++++++++++ .../kbn-saved-objects-settings/kibana.jsonc | 5 +++++ .../kbn-saved-objects-settings/package.json | 6 ++++++ .../kbn-saved-objects-settings/tsconfig.json | 17 +++++++++++++++++ .../server/rpc/routes/routes.ts | 2 +- src/plugins/content_management/tsconfig.json | 2 +- .../saved_objects_finder/common/index.ts | 3 +-- src/plugins/saved_objects_finder/tsconfig.json | 1 + tsconfig.base.json | 2 ++ yarn.lock | 4 ++++ 14 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 packages/kbn-saved-objects-settings/README.md create mode 100644 packages/kbn-saved-objects-settings/index.ts create mode 100644 packages/kbn-saved-objects-settings/jest.config.js create mode 100644 packages/kbn-saved-objects-settings/kibana.jsonc create mode 100644 packages/kbn-saved-objects-settings/package.json create mode 100644 packages/kbn-saved-objects-settings/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 26be09f9bb2f0..a6a4ed6d9b210 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -529,6 +529,7 @@ test/plugin_functional/plugins/saved_objects_hidden_from_http_apis_type @elastic test/plugin_functional/plugins/saved_objects_hidden_type @elastic/kibana-core src/plugins/saved_objects_management @elastic/kibana-core src/plugins/saved_objects @elastic/kibana-core +packages/kbn-saved-objects-settings @elastic/appex-sharedux src/plugins/saved_objects_tagging_oss @elastic/appex-sharedux x-pack/plugins/saved_objects_tagging @elastic/appex-sharedux src/plugins/saved_search @elastic/kibana-data-discovery diff --git a/package.json b/package.json index 2b2f4ff6326f1..83660bd976f88 100644 --- a/package.json +++ b/package.json @@ -531,6 +531,7 @@ "@kbn/saved-objects-hidden-type-plugin": "link:test/plugin_functional/plugins/saved_objects_hidden_type", "@kbn/saved-objects-management-plugin": "link:src/plugins/saved_objects_management", "@kbn/saved-objects-plugin": "link:src/plugins/saved_objects", + "@kbn/saved-objects-settings": "link:packages/kbn-saved-objects-settings", "@kbn/saved-objects-tagging-oss-plugin": "link:src/plugins/saved_objects_tagging_oss", "@kbn/saved-objects-tagging-plugin": "link:x-pack/plugins/saved_objects_tagging", "@kbn/saved-search-plugin": "link:src/plugins/saved_search", diff --git a/packages/kbn-saved-objects-settings/README.md b/packages/kbn-saved-objects-settings/README.md new file mode 100644 index 0000000000000..48e2630354b02 --- /dev/null +++ b/packages/kbn-saved-objects-settings/README.md @@ -0,0 +1,3 @@ +# @kbn/saved-objects-settings + +Contains constants for some of saved objects related ui settings that had to be re-used between plugins without causing circular dependencies. diff --git a/packages/kbn-saved-objects-settings/index.ts b/packages/kbn-saved-objects-settings/index.ts new file mode 100644 index 0000000000000..f2a1aa7c06e54 --- /dev/null +++ b/packages/kbn-saved-objects-settings/index.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 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. + */ + +export const PER_PAGE_SETTING = 'savedObjects:perPage'; +export const LISTING_LIMIT_SETTING = 'savedObjects:listingLimit'; diff --git a/packages/kbn-saved-objects-settings/jest.config.js b/packages/kbn-saved-objects-settings/jest.config.js new file mode 100644 index 0000000000000..562e1f12ecceb --- /dev/null +++ b/packages/kbn-saved-objects-settings/jest.config.js @@ -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 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. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-saved-objects-settings'], +}; diff --git a/packages/kbn-saved-objects-settings/kibana.jsonc b/packages/kbn-saved-objects-settings/kibana.jsonc new file mode 100644 index 0000000000000..40486e1ef0cf1 --- /dev/null +++ b/packages/kbn-saved-objects-settings/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/saved-objects-settings", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/kbn-saved-objects-settings/package.json b/packages/kbn-saved-objects-settings/package.json new file mode 100644 index 0000000000000..38ef61439d47d --- /dev/null +++ b/packages/kbn-saved-objects-settings/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/saved-objects-settings", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-saved-objects-settings/tsconfig.json b/packages/kbn-saved-objects-settings/tsconfig.json new file mode 100644 index 0000000000000..2f9ddddbeea23 --- /dev/null +++ b/packages/kbn-saved-objects-settings/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/src/plugins/content_management/server/rpc/routes/routes.ts b/src/plugins/content_management/server/rpc/routes/routes.ts index 6d40e37d533c8..ea529aae11188 100644 --- a/src/plugins/content_management/server/rpc/routes/routes.ts +++ b/src/plugins/content_management/server/rpc/routes/routes.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import type { IRouter } from '@kbn/core/server'; -import { LISTING_LIMIT_SETTING, PER_PAGE_SETTING } from '@kbn/saved-objects-finder-plugin/common'; +import { LISTING_LIMIT_SETTING, PER_PAGE_SETTING } from '@kbn/saved-objects-settings'; import { ProcedureName } from '../../../common'; import type { ContentRegistry } from '../../core'; import { MSearchService } from '../../core/msearch'; diff --git a/src/plugins/content_management/tsconfig.json b/src/plugins/content_management/tsconfig.json index 93aacaecff0c9..dd87f182de476 100644 --- a/src/plugins/content_management/tsconfig.json +++ b/src/plugins/content_management/tsconfig.json @@ -14,7 +14,7 @@ "@kbn/object-versioning", "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-saved-objects-api-server", - "@kbn/saved-objects-finder-plugin", + "@kbn/saved-objects-settings", ], "exclude": [ "target/**/*", diff --git a/src/plugins/saved_objects_finder/common/index.ts b/src/plugins/saved_objects_finder/common/index.ts index 056d0b4638efc..8a0964edc100b 100644 --- a/src/plugins/saved_objects_finder/common/index.ts +++ b/src/plugins/saved_objects_finder/common/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export const PER_PAGE_SETTING = 'savedObjects:perPage'; -export const LISTING_LIMIT_SETTING = 'savedObjects:listingLimit'; +export { PER_PAGE_SETTING, LISTING_LIMIT_SETTING } from '@kbn/saved-objects-settings'; export type { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP, FinderAttributes } from './types'; diff --git a/src/plugins/saved_objects_finder/tsconfig.json b/src/plugins/saved_objects_finder/tsconfig.json index 813379f02a313..217f4f7f0dbe3 100644 --- a/src/plugins/saved_objects_finder/tsconfig.json +++ b/src/plugins/saved_objects_finder/tsconfig.json @@ -14,6 +14,7 @@ "@kbn/config-schema", "@kbn/core-ui-settings-browser", "@kbn/core-http-browser", + "@kbn/saved-objects-settings", ], "exclude": [ "target/**/*", diff --git a/tsconfig.base.json b/tsconfig.base.json index 85fa70f831719..71c2a3d250a51 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1052,6 +1052,8 @@ "@kbn/saved-objects-management-plugin/*": ["src/plugins/saved_objects_management/*"], "@kbn/saved-objects-plugin": ["src/plugins/saved_objects"], "@kbn/saved-objects-plugin/*": ["src/plugins/saved_objects/*"], + "@kbn/saved-objects-settings": ["packages/kbn-saved-objects-settings"], + "@kbn/saved-objects-settings/*": ["packages/kbn-saved-objects-settings/*"], "@kbn/saved-objects-tagging-oss-plugin": ["src/plugins/saved_objects_tagging_oss"], "@kbn/saved-objects-tagging-oss-plugin/*": ["src/plugins/saved_objects_tagging_oss/*"], "@kbn/saved-objects-tagging-plugin": ["x-pack/plugins/saved_objects_tagging"], diff --git a/yarn.lock b/yarn.lock index c480dd98324b0..365c976479508 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4841,6 +4841,10 @@ version "0.0.0" uid "" +"@kbn/saved-objects-settings@link:packages/kbn-saved-objects-settings": + version "0.0.0" + uid "" + "@kbn/saved-objects-tagging-oss-plugin@link:src/plugins/saved_objects_tagging_oss": version "0.0.0" uid ""