diff --git a/Jenkinsfile b/Jenkinsfile index 3b68cde20657..e33bf67f28df 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,6 @@ #!/bin/groovy +env.KBN_ES_SNAPSHOT_USE_UNVERIFIED = 'true' library 'kibana-pipeline-library' kibanaLibrary.load() diff --git a/docs/developer/contributing/index.asciidoc b/docs/developer/contributing/index.asciidoc index ba4ab89d17c2..bbf2903491bf 100644 --- a/docs/developer/contributing/index.asciidoc +++ b/docs/developer/contributing/index.asciidoc @@ -2,7 +2,7 @@ == Contributing Whether you want to fix a bug, implement a feature, or add some other improvements or apis, the following sections will -guide you on the process. +guide you on the process. After committing your code, check out the link:https://www.elastic.co/community/contributor[Elastic Contributor Program] where you can earn points and rewards for your contributions. Read <> to get your environment up and running, then read <>. diff --git a/package.json b/package.json index b45789172cee..a2c085c0424b 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@elastic/datemath": "link:packages/elastic-datemath", "@elastic/elasticsearch": "7.10.0-rc.1", "@elastic/ems-client": "7.10.0", - "@elastic/eui": "30.1.1", + "@elastic/eui": "30.2.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/node-crypto": "1.2.1", diff --git a/src/cli/dev.js b/src/cli/dev.js index a284c82dfeb6..99b7c1a696e4 100644 --- a/src/cli/dev.js +++ b/src/cli/dev.js @@ -19,4 +19,5 @@ require('../apm')(process.env.ELASTIC_APM_SERVICE_NAME || 'kibana-proxy'); require('../setup_node_env'); +require('../setup_node_env/root'); require('./cli'); diff --git a/src/cli/dist.js b/src/cli/dist.js index 05f0a68aa495..bc14a3b53035 100644 --- a/src/cli/dist.js +++ b/src/cli/dist.js @@ -19,4 +19,5 @@ require('../apm')(); require('../setup_node_env/dist'); +require('../setup_node_env/root'); require('./cli'); diff --git a/src/cli_keystore/dev.js b/src/cli_keystore/dev.js index 12dc51134aad..c229d26439bb 100644 --- a/src/cli_keystore/dev.js +++ b/src/cli_keystore/dev.js @@ -17,5 +17,5 @@ * under the License. */ -require('../setup_node_env'); +require('../setup_node_env/no_transpilation'); require('./cli_keystore'); diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 274d7a4e5a48..4c833f5be6c5 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -237,6 +237,7 @@ kibana_vars=( xpack.security.authc.oidc.realm xpack.security.authc.saml.realm xpack.security.authc.saml.maxRedirectURLSize + xpack.security.authc.selector.enabled xpack.security.cookieName xpack.security.enabled xpack.security.encryptionKey diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 8679cce9b11f..19487efe1366 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -40,7 +40,7 @@ export async function runDockerGenerator( ubi: boolean = false ) { // UBI var config - const baseOSImage = ubi ? 'docker.elastic.co/ubi8/ubi-minimal:8.2' : 'centos:8'; + const baseOSImage = ubi ? 'docker.elastic.co/ubi8/ubi-minimal:latest' : 'centos:8'; const ubiVersionTag = 'ubi8'; const ubiImageFlavor = ubi ? `-${ubiVersionTag}` : ''; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile index d17b597eb664..9c78821f331e 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile @@ -46,16 +46,9 @@ EXPOSE 5601 {{#ubi}} # https://github.com/rpm-software-management/microdnf/issues/50 RUN mkdir -p /run/user/$(id -u) - - # crypto-policies not currently compatible with libnss :sadpanda: - RUN printf "[main]\nexclude=crypto-policies" > /etc/dnf/dnf.conf {{/ubi}} RUN for iter in {1..10}; do \ - {{#ubi}} - # update microdnf to have exclusion feature for dnf configuration - {{packageManager}} update microdnf --setopt=tsflags=nodocs -y && \ - {{/ubi}} {{packageManager}} update --setopt=tsflags=nodocs -y && \ {{packageManager}} install --setopt=tsflags=nodocs -y \ fontconfig freetype shadow-utils libnss3.so {{#ubi}}findutils{{/ubi}} && \ diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 6eb111e066c8..83ed63005804 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -44,9 +44,11 @@ case $1 in IS_UPGRADE=true fi + PACKAGE=deb setup ;; abort-deconfigure|abort-upgrade|abort-remove) + PACKAGE=deb ;; # Red Hat @@ -63,7 +65,8 @@ case $1 in if [ "$1" = "2" ]; then IS_UPGRADE=true fi - + + PACKAGE=rpm setup ;; @@ -86,3 +89,13 @@ if [ "$IS_UPGRADE" = "true" ]; then echo " OK" fi fi + +# the equivalent code for rpm is in posttrans +if [ "$PACKAGE" = "deb" ]; then + if [ ! -f "${KBN_PATH_CONF}"/kibana.keystore ]; then + /usr/share/kibana/bin/kibana-keystore create + chown root:<%= group %> "${KBN_PATH_CONF}"/kibana.keystore + chmod 660 "${KBN_PATH_CONF}"/kibana.keystore + md5sum "${KBN_PATH_CONF}"/kibana.keystore > "${KBN_PATH_CONF}"/.kibana.keystore.initial_md5sum + fi +fi diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_trans.sh b/src/dev/build/tasks/os_packages/package_scripts/post_trans.sh new file mode 100644 index 000000000000..3c1bd3ccf88b --- /dev/null +++ b/src/dev/build/tasks/os_packages/package_scripts/post_trans.sh @@ -0,0 +1,8 @@ +export KBN_PATH_CONF=${KBN_PATH_CONF:-<%= configDir %>} + +if [ ! -f "${KBN_PATH_CONF}"/kibana.keystore ]; then + /usr/share/kibana/bin/kibana-keystore create + chown root:<%= group %> "${KBN_PATH_CONF}"/kibana.keystore + chmod 660 "${KBN_PATH_CONF}"/kibana.keystore + md5sum "${KBN_PATH_CONF}"/kibana.keystore > "${KBN_PATH_CONF}"/.kibana.keystore.initial_md5sum +fi diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index 7dff592eb9b8..def0289f5364 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -45,6 +45,8 @@ export async function runFpm( } }; + const envFolder = type === 'rpm' ? 'sysconfig' : 'default'; + const args = [ // Force output even if it will overwrite an existing file '--force', @@ -92,6 +94,8 @@ export async function runFpm( resolve(__dirname, 'package_scripts/pre_remove.sh'), '--after-remove', resolve(__dirname, 'package_scripts/post_remove.sh'), + '--rpm-posttrans', + resolve(__dirname, 'package_scripts/post_trans.sh'), // tell fpm about the config file so that it is called out in the package definition '--config-files', @@ -140,6 +144,11 @@ export async function runFpm( // copy package configurations `${resolveWithTrailingSlash(__dirname, 'service_templates/systemd/')}=/`, + + `${resolveWithTrailingSlash( + __dirname, + 'service_templates/env/kibana' + )}=/etc/${envFolder}/kibana`, ]; log.debug('calling fpm with args:', args); diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana b/src/dev/build/tasks/os_packages/service_templates/env/kibana similarity index 100% rename from src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana rename to src/dev/build/tasks/os_packages/service_templates/env/kibana diff --git a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap index 31ef01183591..dac84c87faf9 100644 --- a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -297,7 +297,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = ` paddingSize="none" >
+ str.replace(/\(opens in a new tab or window\)/g, ''); + describe('LinkPreview', () => { let callApmApiSpy: jest.SpyInstance; beforeAll(() => { @@ -53,7 +56,9 @@ describe('LinkPreview', () => { ); expect(getElementValue(container, 'preview-label')).toEqual('foo'); expect( - (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + removeExternalLinkText( + (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + ) ).toEqual('https://baz.co'); }); }); @@ -69,7 +74,9 @@ describe('LinkPreview', () => { ); expect(getElementValue(container, 'preview-label')).toEqual('foo'); expect( - (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + removeExternalLinkText( + (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + ) ).toEqual('https://baz.co?service.name={{invalid}'); expect(getByTestId(container, 'preview-warning')).toBeInTheDocument(); }); @@ -85,7 +92,9 @@ describe('LinkPreview', () => { await waitFor(() => expect(callApmApiSpy).toHaveBeenCalled()); expect(getElementValue(container, 'preview-label')).toEqual('foo'); expect( - (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + removeExternalLinkText( + (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + ) ).toEqual('https://baz.co?transaction=foo'); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx index 30d4bb34ea34..c453de709a5d 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx @@ -19,6 +19,6 @@ test('MLLink produces the correct URL', async () => { ); expect(href).toMatchInlineSnapshot( - `"/app/ml/jobs?_a=(queryText:'id:(something)%20groups:(apm)')&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"` + `"/app/ml/jobs?_a=(jobs:(queryText:'id:(something)%20groups:(apm)'))&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"` ); }); diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot index e62785272a5e..05339ca55856 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot @@ -12,7 +12,7 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` className="euiFlexItem" >
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap index 06cd77558b44..ba2ec28bf6bc 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap @@ -50,7 +50,7 @@ exports[`policy table should show empty state when there are not any policies 1` class="euiPageBody" >
{ + const [showLoading, setShowLoading] = useState(true); const { sourceId, source } = useSourceContext(); const { currentView, shouldLoadDefault } = useSavedViewContext(); const { @@ -100,6 +101,16 @@ export const Layout = () => { } }, [reload, currentView, shouldLoadDefault]); + useEffect(() => { + setShowLoading(true); + }, [options.metric, nodeType]); + + useEffect(() => { + const hasNodes = nodes && nodes.length; + // Don't show loading screen when we're auto-reloading + setShowLoading(!hasNodes); + }, [nodes]); + return ( <> @@ -130,6 +141,7 @@ export const Layout = () => { options={options} nodeType={nodeType} loading={loading} + showLoading={showLoading} reload={reload} onDrilldown={applyFilterQuery} currentTime={currentTime} diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx index aa6157dc48d5..9b6853dcdc75 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx @@ -37,6 +37,7 @@ interface Props { formatter: InfraFormatter; bottomMargin: number; topMargin: number; + showLoading: boolean; } export const NodesOverview = ({ @@ -53,6 +54,7 @@ export const NodesOverview = ({ onDrilldown, bottomMargin, topMargin, + showLoading, }: Props) => { const handleDrilldown = useCallback( (filter: string) => { @@ -66,7 +68,8 @@ export const NodesOverview = ({ ); const noData = !loading && nodes && nodes.length === 0; - if (loading) { + if (loading && showLoading) { + // Don't show loading screen when we're auto-reloading return (
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; -exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap index db37bc6cb98c..719fce35a2a6 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RequestTrialExtension component should display when enterprise license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when enterprise license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features(opens in a new tab or window), request an extension now.

"`; -exports[`RequestTrialExtension component should display when license is active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when license is active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features(opens in a new tab or window), request an extension now.

"`; -exports[`RequestTrialExtension component should display when license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features(opens in a new tab or window), request an extension now.

"`; -exports[`RequestTrialExtension component should display when platinum license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when platinum license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features(opens in a new tab or window), request an extension now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap index 24598635b28e..a0f3948785a8 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RevertToBasic component should display when license is about to expire 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features.

"`; +exports[`RevertToBasic component should display when license is about to expire 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features(opens in a new tab or window).

"`; -exports[`RevertToBasic component should display when license is expired 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features.

"`; +exports[`RevertToBasic component should display when license is expired 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features(opens in a new tab or window).

"`; -exports[`RevertToBasic component should display when trial is active 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features.

"`; +exports[`RevertToBasic component should display when trial is active 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features(opens in a new tab or window).

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap index 24872a888b09..52a2da596c10 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StartTrial component when trial is allowed display for basic license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; +exports[`StartTrial component when trial is allowed display for basic license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features(opens in a new tab or window) have to offer.

"`; -exports[`StartTrial component when trial is allowed should display for expired enterprise license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; +exports[`StartTrial component when trial is allowed should display for expired enterprise license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features(opens in a new tab or window) have to offer.

"`; -exports[`StartTrial component when trial is allowed should display for expired platinum license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; +exports[`StartTrial component when trial is allowed should display for expired platinum license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features(opens in a new tab or window) have to offer.

"`; -exports[`StartTrial component when trial is allowed should display for gold license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; +exports[`StartTrial component when trial is allowed should display for gold license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features(opens in a new tab or window) have to offer.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 72c04992566b..cc41a3a4b4e2 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -270,7 +270,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem paddingSize="l" >
{ [id: string]: TValue; } @@ -31,3 +33,15 @@ export type DeepReadonly = T extends Array type DeepReadonlyObject = { readonly [P in keyof T]: DeepReadonly; }; + +export interface ListingPageUrlState { + pageSize: number; + pageIndex: number; + sortField: string; + sortDirection: string; + queryText?: string; +} + +export type AppPageState = { + [key in MlPages]?: Partial; +}; diff --git a/x-pack/plugins/ml/common/util/string_utils.ts b/x-pack/plugins/ml/common/util/string_utils.ts index 4691bac0a065..ffb8b19dc9aa 100644 --- a/x-pack/plugins/ml/common/util/string_utils.ts +++ b/x-pack/plugins/ml/common/util/string_utils.ts @@ -45,5 +45,5 @@ export function getGroupQueryText(groupIds: string[]): string { } export function getJobQueryText(jobIds: string | string[]): string { - return Array.isArray(jobIds) ? `id:(${jobIds.join(' OR ')})` : jobIds; + return Array.isArray(jobIds) ? `id:(${jobIds.join(' OR ')})` : `id:${jobIds}`; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 17ef84179ce6..63b7074ec3aa 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -7,10 +7,10 @@ import React, { FC, useCallback, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { + EuiInMemoryTable, EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiInMemoryTable, EuiSearchBar, EuiSearchBarProps, EuiSpacer, @@ -30,13 +30,12 @@ import { getTaskStateBadge, getJobTypeBadge, useColumns } from './use_columns'; import { ExpandedRow } from './expanded_row'; import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar'; import { CreateAnalyticsButton } from '../create_analytics_button'; -import { getSelectedIdFromUrl } from '../../../../../jobs/jobs_list/components/utils'; import { SourceSelection } from '../source_selection'; import { filterAnalytics } from '../../../../common/search_bar_filters'; import { AnalyticsEmptyPrompt } from './empty_prompt'; import { useTableSettings } from './use_table_settings'; import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button'; -import { getGroupQueryText } from '../../../../../../../common/util/string_utils'; +import { ListingPageUrlState } from '../../../../../../../common/types/common'; const filters: EuiSearchBarProps['filters'] = [ { @@ -84,17 +83,28 @@ interface Props { isManagementTable?: boolean; isMlEnabledInSpace?: boolean; blockRefresh?: boolean; + pageState: ListingPageUrlState; + updatePageState: (update: Partial) => void; } export const DataFrameAnalyticsList: FC = ({ isManagementTable = false, isMlEnabledInSpace = true, blockRefresh = false, + pageState, + updatePageState, }) => { + const searchQueryText = pageState.queryText ?? ''; + const setSearchQueryText = useCallback( + (value) => { + updatePageState({ queryText: value }); + }, + [updatePageState] + ); + const [isInitialized, setIsInitialized] = useState(false); const [isSourceIndexModalVisible, setIsSourceIndexModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [filteredAnalytics, setFilteredAnalytics] = useState([]); - const [searchQueryText, setSearchQueryText] = useState(''); const [searchError, setSearchError] = useState(); const [analytics, setAnalytics] = useState([]); const [analyticsStats, setAnalyticsStats] = useState( @@ -102,9 +112,6 @@ export const DataFrameAnalyticsList: FC = ({ ); const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); - // Query text/job_id based on url but only after getAnalytics is done first - // selectedJobIdFromUrlInitialized makes sure the query is only run once since analytics is being refreshed constantly - const [selectedIdFromUrlInitialized, setSelectedIdFromUrlInitialized] = useState(false); const disabled = !checkPermission('canCreateDataFrameAnalytics') || @@ -119,17 +126,20 @@ export const DataFrameAnalyticsList: FC = ({ isManagementTable ); - const updateFilteredItems = (queryClauses: any) => { - if (queryClauses.length) { - const filtered = filterAnalytics(analytics, queryClauses); - setFilteredAnalytics(filtered); - } else { - setFilteredAnalytics(analytics); - } - }; + const updateFilteredItems = useCallback( + (queryClauses: any[]) => { + if (queryClauses.length) { + const filtered = filterAnalytics(analytics, queryClauses); + setFilteredAnalytics(filtered); + } else { + setFilteredAnalytics(analytics); + } + }, + [analytics] + ); const filterList = () => { - if (searchQueryText !== '' && selectedIdFromUrlInitialized === true) { + if (searchQueryText !== '') { // trigger table filtering with query for job id to trigger table filter const query = EuiSearchBar.Query.parse(searchQueryText); let clauses: any = []; @@ -142,27 +152,9 @@ export const DataFrameAnalyticsList: FC = ({ } }; - useEffect(() => { - if (selectedIdFromUrlInitialized === false && analytics.length > 0) { - const { jobId, groupIds } = getSelectedIdFromUrl(window.location.href); - let queryText = ''; - - if (groupIds !== undefined) { - queryText = getGroupQueryText(groupIds); - } else if (jobId !== undefined) { - queryText = jobId; - } - - setSelectedIdFromUrlInitialized(true); - setSearchQueryText(queryText); - } else { - filterList(); - } - }, [selectedIdFromUrlInitialized, analytics]); - useEffect(() => { filterList(); - }, [selectedIdFromUrlInitialized, searchQueryText]); + }, [searchQueryText]); const getAnalyticsCallback = useCallback(() => getAnalytics(true), []); @@ -183,19 +175,19 @@ export const DataFrameAnalyticsList: FC = ({ ); const { onTableChange, pagination, sorting } = useTableSettings( - DataFrameAnalyticsListColumn.id, - filteredAnalytics + filteredAnalytics, + pageState, + updatePageState ); const handleSearchOnChange: EuiSearchBarProps['onChange'] = (search) => { if (search.error !== null) { setSearchError(search.error.message); - return false; + return; } setSearchError(undefined); setSearchQueryText(search.queryText); - return true; }; // Before the analytics have been loaded for the first time, display the loading indicator only. @@ -251,6 +243,7 @@ export const DataFrameAnalyticsList: FC = ({ ); + const search: EuiSearchBarProps = { query: searchQueryText, onChange: handleSearchOnChange, @@ -284,15 +277,13 @@ export const DataFrameAnalyticsList: FC = ({
allowNeutralSort={false} - className="mlAnalyticsInMemoryTable" columns={columns} - error={searchError} hasActions={false} isExpandable={true} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} isSelectable={false} items={analytics} itemId={DataFrameAnalyticsListColumn.id} - itemIdToExpandedRowMap={itemIdToExpandedRowMap} loading={isLoading} onTableChange={onTableChange} pagination={pagination} @@ -302,6 +293,7 @@ export const DataFrameAnalyticsList: FC = ({ rowProps={(item) => ({ 'data-test-subj': `mlAnalyticsTableRow row-${item.id}`, })} + error={searchError} />
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index 8c7c8b9db8b6..84c37ac8b816 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -116,14 +116,14 @@ export interface DataFrameAnalyticsListRow { } // Used to pass on attribute names to table columns -export enum DataFrameAnalyticsListColumn { - configDestIndex = 'config.dest.index', - configSourceIndex = 'config.source.index', - configCreateTime = 'config.create_time', - description = 'config.description', - id = 'id', - memoryStatus = 'stats.memory_usage.status', -} +export const DataFrameAnalyticsListColumn = { + configDestIndex: 'config.dest.index', + configSourceIndex: 'config.source.index', + configCreateTime: 'config.create_time', + description: 'config.description', + id: 'id', + memoryStatus: 'stats.memory_usage.status', +} as const; export type ItemIdToExpandedRowMap = Record; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx index 2b63b9e78081..93868ce0c17e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx @@ -135,13 +135,13 @@ export const progressColumn = { 'data-test-subj': 'mlAnalyticsTableColumnProgress', }; -export const DFAnalyticsJobIdLink = ({ item }: { item: DataFrameAnalyticsListRow }) => { +export const DFAnalyticsJobIdLink = ({ jobId }: { jobId: string }) => { const href = useMlLink({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, - pageState: { jobId: item.id }, + pageState: { jobId }, }); - return {item.id}; + return {jobId}; }; export const useColumns = ( @@ -199,13 +199,17 @@ export const useColumns = ( 'data-test-subj': 'mlAnalyticsTableRowDetailsToggle', }, { - name: 'ID', + field: DataFrameAnalyticsListColumn.id, + name: i18n.translate('xpack.ml.dataframe.analyticsList.id', { + defaultMessage: 'ID', + }), sortable: (item: DataFrameAnalyticsListRow) => item.id, truncateText: true, 'data-test-subj': 'mlAnalyticsTableColumnId', scope: 'row', - render: (item: DataFrameAnalyticsListRow) => - isManagementTable ? : item.id, + render: (jobId: string) => { + return isManagementTable ? : jobId; + }, }, { field: DataFrameAnalyticsListColumn.description, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts index 5b7d71dacccf..68774fb86fe9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState } from 'react'; import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui'; +import { useCallback, useMemo } from 'react'; +import { ListingPageUrlState } from '../../../../../../../common/types/common'; -const PAGE_SIZE = 10; const PAGE_SIZE_OPTIONS = [10, 25, 50]; // Copying from EUI EuiBasicTable types as type is not correctly picked up for table's onChange @@ -29,15 +29,6 @@ export interface CriteriaWithPagination extends Criteria { }; } -interface AnalyticsBasicTableSettings { - pageIndex: number; - pageSize: number; - totalItemCount: number; - hidePerPageOptions: boolean; - sortField: keyof T; - sortDirection: Direction; -} - interface UseTableSettingsReturnValue { onTableChange: EuiBasicTableProps['onChange']; pagination: Pagination; @@ -45,49 +36,44 @@ interface UseTableSettingsReturnValue { } export function useTableSettings( - sortByField: keyof TypeOfItem, - items: TypeOfItem[] + items: TypeOfItem[], + pageState: ListingPageUrlState, + updatePageState: (update: Partial) => void ): UseTableSettingsReturnValue { - const [tableSettings, setTableSettings] = useState>({ - pageIndex: 0, - pageSize: PAGE_SIZE, - totalItemCount: 0, - hidePerPageOptions: false, - sortField: sortByField, - sortDirection: 'asc', - }); - - const onTableChange: EuiBasicTableProps['onChange'] = ({ - page = { index: 0, size: PAGE_SIZE }, - sort = { field: sortByField, direction: 'asc' }, - }: CriteriaWithPagination) => { - const { index, size } = page; - const { field, direction } = sort; - - setTableSettings({ - ...tableSettings, - pageIndex: index, - pageSize: size, - sortField: field, - sortDirection: direction, - }); - }; + const { pageIndex, pageSize, sortField, sortDirection } = pageState; - const { pageIndex, pageSize, sortField, sortDirection } = tableSettings; + const onTableChange: EuiBasicTableProps['onChange'] = useCallback( + ({ page, sort }: CriteriaWithPagination) => { + const result = { + pageIndex: page?.index ?? pageState.pageIndex, + pageSize: page?.size ?? pageState.pageSize, + sortField: (sort?.field as string) ?? pageState.sortField, + sortDirection: sort?.direction ?? pageState.sortDirection, + }; + updatePageState(result); + }, + [pageState, updatePageState] + ); - const pagination = { - pageIndex, - pageSize, - totalItemCount: items.length, - pageSizeOptions: PAGE_SIZE_OPTIONS, - }; + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + totalItemCount: items.length, + pageSizeOptions: PAGE_SIZE_OPTIONS, + }), + [items, pageIndex, pageSize] + ); - const sorting = { - sort: { - field: sortField as string, - direction: sortDirection, - }, - }; + const sorting = useMemo( + () => ({ + sort: { + field: sortField as string, + direction: sortDirection as Direction, + }, + }), + [sortField, sortDirection] + ); return { onTableChange, pagination, sorting }; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx index eaeae6cc6452..a5d3555fcc27 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx @@ -50,9 +50,12 @@ export const AnalyticsNavigationBar: FC<{ selectedTabId?: string; jobId?: string return navTabs; }, [jobId !== undefined]); - const onTabClick = useCallback(async (tab: Tab) => { - await navigateToPath(tab.path, true); - }, []); + const onTabClick = useCallback( + async (tab: Tab) => { + await navigateToPath(tab.path, true); + }, + [navigateToPath] + ); return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts index 7c70a2507164..77c794dce10c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts @@ -6,8 +6,8 @@ export * from './models_list'; -export enum ModelsTableToConfigMapping { - id = 'model_id', - createdAt = 'create_time', - type = 'type', -} +export const ModelsTableToConfigMapping = { + id: 'model_id', + createdAt: 'create_time', + type: 'type', +} as const; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index a87f11df937d..2d74d08c4550 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -52,6 +52,8 @@ import { filterAnalyticsModels } from '../../../../common/search_bar_filters'; import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator'; import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics'; import { timeFormatter } from '../../../../../../../common/util/date_utils'; +import { ListingPageUrlState } from '../../../../../../../common/types/common'; +import { usePageUrlState } from '../../../../../util/url_state'; type Stats = Omit; @@ -63,6 +65,13 @@ export type ModelItem = TrainedModelConfigResponse & { export type ModelItemFull = Required; +export const getDefaultModelsListState = (): ListingPageUrlState => ({ + pageIndex: 0, + pageSize: 10, + sortField: ModelsTableToConfigMapping.id, + sortDirection: 'asc', +}); + export const ModelsList: FC = () => { const { services: { @@ -71,12 +80,24 @@ export const ModelsList: FC = () => { } = useMlKibana(); const urlGenerator = useMlUrlGenerator(); + const [pageState, updatePageState] = usePageUrlState( + ML_PAGES.DATA_FRAME_ANALYTICS_MODELS_MANAGE, + getDefaultModelsListState() + ); + + const searchQueryText = pageState.queryText ?? ''; + const setSearchQueryText = useCallback( + (value) => { + updatePageState({ queryText: value }); + }, + [updatePageState] + ); + const canDeleteDataFrameAnalytics = capabilities.ml.canDeleteDataFrameAnalytics as boolean; const trainedModelsApiService = useTrainedModelsApiService(); const { toasts } = useNotifications(); - const [searchQueryText, setSearchQueryText] = useState(''); const [filteredModels, setFilteredModels] = useState([]); const [isLoading, setIsLoading] = useState(false); const [items, setItems] = useState([]); @@ -432,8 +453,9 @@ export const ModelsList: FC = () => { : []; const { onTableChange, pagination, sorting } = useTableSettings( - ModelsTableToConfigMapping.id, - filteredModels + filteredModels, + pageState, + updatePageState ); const toolsLeft = ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx index c31a3b08aa75..5a17b91818a1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx @@ -33,11 +33,27 @@ import { UpgradeWarning } from '../../../components/upgrade'; import { AnalyticsNavigationBar } from './components/analytics_navigation_bar'; import { ModelsList } from './components/models_management'; import { JobMap } from '../job_map'; +import { usePageUrlState } from '../../../util/url_state'; +import { ListingPageUrlState } from '../../../../../common/types/common'; +import { DataFrameAnalyticsListColumn } from './components/analytics_list/common'; +import { ML_PAGES } from '../../../../../common/constants/ml_url_generator'; + +export const getDefaultDFAListState = (): ListingPageUrlState => ({ + pageIndex: 0, + pageSize: 10, + sortField: DataFrameAnalyticsListColumn.id, + sortDirection: 'asc', +}); export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); const [globalState] = useUrlState('_g'); + const [dfaPageState, setDfaPageState] = usePageUrlState( + ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, + getDefaultDFAListState() + ); + useRefreshInterval(setBlockRefresh); const location = useLocation(); @@ -93,7 +109,11 @@ export const Page: FC = () => { {selectedTabId === 'map' && mapJobId && } {selectedTabId === 'data_frame_analytics' && ( - + )} {selectedTabId === 'models' && } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx index f0fa62b7a3d8..1b1bea889925 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx @@ -22,8 +22,6 @@ import { JobGroup } from '../job_group'; import { useMlKibana } from '../../../../contexts/kibana'; interface JobFilterBarProps { - jobId: string; - groupIds: string[]; setFilters: (query: Query | null) => void; queryText?: string; } @@ -75,7 +73,7 @@ export const JobFilterBar: FC = ({ queryText, setFilters }) = useEffect(() => { setFilters(queryInstance); - }, []); + }, [queryText]); const filters: SearchFilterConfig[] = useMemo( () => [ diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts deleted file mode 100644 index b781199c8523..000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts +++ /dev/null @@ -1,8 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export function getSelectedIdFromUrl(str: string): { groupIds?: string[]; jobId?: string }; -export function clearSelectedJobIdFromUrl(str: string): void; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 397062248689..338222e3ac4a 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -6,7 +6,6 @@ import { each } from 'lodash'; import { i18n } from '@kbn/i18n'; -import rison from 'rison-node'; import { mlJobService } from '../../../services/job_service'; import { @@ -367,31 +366,3 @@ function jobProperty(job, prop) { }; return job[propMap[prop]]; } - -function getUrlVars(url) { - const vars = {}; - url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (_, key, value) { - vars[key] = value; - }); - return vars; -} - -export function getSelectedIdFromUrl(url) { - const result = {}; - if (typeof url === 'string') { - const isGroup = url.includes('groupIds'); - url = decodeURIComponent(url); - - if (url.includes('mlManagement')) { - const urlParams = getUrlVars(url); - const decodedJson = rison.decode(urlParams.mlManagement); - - if (isGroup) { - result.groupIds = decodedJson.groupIds; - } else { - result.jobId = decodedJson.jobId; - } - } - } - return result; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts deleted file mode 100644 index 4414be0b4fdc..000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts +++ /dev/null @@ -1,35 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getSelectedIdFromUrl } from './utils'; - -describe('ML - Jobs List utils', () => { - const jobId = 'test_job_id_1'; - const jobIdUrl = `http://localhost:5601/aql/app/ml#/jobs?mlManagement=(jobId:${jobId})`; - const groupIdOne = 'test_group_id_1'; - const groupIdTwo = 'test_group_id_2'; - const groupIdsUrl = `http://localhost:5601/aql/app/ml#/jobs?mlManagement=(groupIds:!(${groupIdOne},${groupIdTwo}))`; - const groupIdUrl = `http://localhost:5601/aql/app/ml#/jobs?mlManagement=(groupIds:!(${groupIdOne}))`; - - describe('getSelectedIdFromUrl', () => { - it('should get selected job id from the url', () => { - const actual = getSelectedIdFromUrl(jobIdUrl); - expect(actual).toStrictEqual({ jobId }); - }); - - it('should get selected group ids from the url', () => { - const expected = { groupIds: [groupIdOne, groupIdTwo] }; - const actual = getSelectedIdFromUrl(groupIdsUrl); - expect(actual).toStrictEqual(expected); - }); - - it('should get selected group id from the url', () => { - const expected = { groupIds: [groupIdOne] }; - const actual = getSelectedIdFromUrl(groupIdUrl); - expect(actual).toStrictEqual(expected); - }); - }); -}); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx index 4c6469f6800a..df50f53b811f 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useCallback, useMemo } from 'react'; +import React, { FC } from 'react'; import { NavigationMenu } from '../../components/navigation_menu'; // @ts-ignore import { JobsListView } from './components/jobs_list_view/index'; -import { useUrlState } from '../../util/url_state'; +import { usePageUrlState } from '../../util/url_state'; +import { ML_PAGES } from '../../../../common/constants/ml_url_generator'; +import { ListingPageUrlState } from '../../../../common/types/common'; interface JobsPageProps { blockRefresh?: boolean; @@ -17,15 +19,7 @@ interface JobsPageProps { lastRefresh?: number; } -export interface AnomalyDetectionJobsListState { - pageSize: number; - pageIndex: number; - sortField: string; - sortDirection: string; - queryText?: string; -} - -export const getDefaultAnomalyDetectionJobsListState = (): AnomalyDetectionJobsListState => ({ +export const getDefaultAnomalyDetectionJobsListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, sortField: 'id', @@ -33,33 +27,15 @@ export const getDefaultAnomalyDetectionJobsListState = (): AnomalyDetectionJobsL }); export const JobsPage: FC = (props) => { - const [appState, setAppState] = useUrlState('_a'); - - const jobListState: AnomalyDetectionJobsListState = useMemo(() => { - return { - ...getDefaultAnomalyDetectionJobsListState(), - ...(appState ?? {}), - }; - }, [appState]); - - const onJobsViewStateUpdate = useCallback( - (update: Partial) => { - setAppState({ - ...jobListState, - ...update, - }); - }, - [appState, setAppState] + const [pageState, setPageState] = usePageUrlState( + ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + getDefaultAnomalyDetectionJobsListState() ); return (
- +
); }; diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index ad4b9ad78902..1089484449ba 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -35,11 +35,10 @@ import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_vi import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list'; import { AccessDeniedPage } from '../access_denied_page'; import { SharePluginStart } from '../../../../../../../../../src/plugins/share/public'; -import { - AnomalyDetectionJobsListState, - getDefaultAnomalyDetectionJobsListState, -} from '../../../../jobs/jobs_list/jobs'; +import { getDefaultAnomalyDetectionJobsListState } from '../../../../jobs/jobs_list/jobs'; import { getMlGlobalServices } from '../../../../app'; +import { ListingPageUrlState } from '../../../../../../common/types/common'; +import { getDefaultDFAListState } from '../../../../data_frame_analytics/pages/analytics_management/page'; interface Tab { 'data-test-subj': string; @@ -48,21 +47,28 @@ interface Tab { content: any; } -function useTabs(isMlEnabledInSpace: boolean): Tab[] { - const [jobsViewState, setJobsViewState] = useState( - getDefaultAnomalyDetectionJobsListState() - ); +function usePageState( + defaultState: T +): [T, (update: Partial) => void] { + const [pageState, setPageState] = useState(defaultState); const updateState = useCallback( - (update: Partial) => { - setJobsViewState({ - ...jobsViewState, + (update: Partial) => { + setPageState({ + ...pageState, ...update, }); }, - [jobsViewState] + [pageState] ); + return [pageState, updateState]; +} + +function useTabs(isMlEnabledInSpace: boolean): Tab[] { + const [adPageState, updateAdPageState] = usePageState(getDefaultAnomalyDetectionJobsListState()); + const [dfaPageState, updateDfaPageState] = usePageState(getDefaultDFAListState()); + return useMemo( () => [ { @@ -75,8 +81,8 @@ function useTabs(isMlEnabledInSpace: boolean): Tab[] { @@ -95,12 +101,14 @@ function useTabs(isMlEnabledInSpace: boolean): Tab[] { ), }, ], - [isMlEnabledInSpace, jobsViewState, updateState] + [isMlEnabledInSpace, adPageState, updateAdPageState, dfaPageState, updateDfaPageState] ); } diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/plugins/ml/public/application/util/url_state.tsx index a3c70e113090..448a888ab32c 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -13,6 +13,7 @@ import { useHistory, useLocation } from 'react-router-dom'; import { Dictionary } from '../../../common/types/common'; import { getNestedProperty } from './object_utils'; +import { MlPages } from '../../../common/constants/ml_url_generator'; type Accessor = '_a' | '_g'; export type SetUrlState = ( @@ -150,3 +151,35 @@ export const useUrlState = (accessor: Accessor) => { ); return [urlState, setUrlState]; }; + +/** + * Hook for managing the URL state of the page. + */ +export const usePageUrlState = ( + pageKey: MlPages, + defaultState: PageUrlState +): [PageUrlState, (update: Partial) => void] => { + const [appState, setAppState] = useUrlState('_a'); + const pageState = appState?.[pageKey]; + + const resultPageState: PageUrlState = useMemo(() => { + return { + ...defaultState, + ...(pageState ?? {}), + }; + }, [pageState]); + + const onStateUpdate = useCallback( + (update: Partial, replace?: boolean) => { + setAppState(pageKey, { + ...(replace ? {} : resultPageState), + ...update, + }); + }, + [pageKey, resultPageState, setAppState] + ); + + return useMemo(() => { + return [resultPageState, onStateUpdate]; + }, [resultPageState, onStateUpdate]); +}; diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts index 717d293ccd7f..6d7e286a2947 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts @@ -19,8 +19,8 @@ import type { import { ML_PAGES } from '../../common/constants/ml_url_generator'; import { createGenericMlUrl } from './common'; import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; -import type { AnomalyDetectionJobsListState } from '../application/jobs/jobs_list/jobs'; import { getGroupQueryText, getJobQueryText } from '../../common/util/string_utils'; +import { AppPageState, ListingPageUrlState } from '../../common/types/common'; /** * Creates URL to the Anomaly Detection Job management page */ @@ -41,11 +41,15 @@ export function createAnomalyDetectionJobManagementUrl( if (groupIds) { queryTextArr.push(getGroupQueryText(groupIds)); } - const queryState: Partial = { + const jobsListState: Partial = { ...(queryTextArr.length > 0 ? { queryText: queryTextArr.join(' ') } : {}), }; - url = setStateToKbnUrl>( + const queryState: AppPageState = { + [ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE]: jobsListState, + }; + + url = setStateToKbnUrl>( '_a', queryState, { useHash: false, storeInHashQuery: false }, diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts index 6c58a9d28bcc..dc9c3bd86cc6 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts @@ -10,12 +10,13 @@ import { DataFrameAnalyticsExplorationQueryState, DataFrameAnalyticsExplorationUrlState, - DataFrameAnalyticsQueryState, DataFrameAnalyticsUrlState, MlCommonGlobalState, } from '../../common/types/ml_url_generator'; import { ML_PAGES } from '../../common/constants/ml_url_generator'; import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; +import { getGroupQueryText, getJobQueryText } from '../../common/util/string_utils'; +import { AppPageState, ListingPageUrlState } from '../../common/types/common'; export function createDataFrameAnalyticsJobManagementUrl( appBasePath: string, @@ -26,13 +27,23 @@ export function createDataFrameAnalyticsJobManagementUrl( if (mlUrlGeneratorState) { const { jobId, groupIds, globalState } = mlUrlGeneratorState; if (jobId || groupIds) { - const queryState: Partial = { - jobId, - groupIds, + const queryTextArr = []; + if (jobId) { + queryTextArr.push(getJobQueryText(jobId)); + } + if (groupIds) { + queryTextArr.push(getGroupQueryText(groupIds)); + } + const jobsListState: Partial = { + ...(queryTextArr.length > 0 ? { queryText: queryTextArr.join(' ') } : {}), + }; + + const queryState: AppPageState = { + [ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE]: jobsListState, }; - url = setStateToKbnUrl>( - 'mlManagement', + url = setStateToKbnUrl>( + '_a', queryState, { useHash: false, storeInHashQuery: false }, url diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts index e7f12ead3ffe..3f3d88f1a31d 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts @@ -30,7 +30,7 @@ describe('MlUrlGenerator', () => { jobId: 'fq_single_1', }, }); - expect(url).toBe('/app/ml/jobs?_a=(queryText:fq_single_1)'); + expect(url).toBe("/app/ml/jobs?_a=(jobs:(queryText:'id:fq_single_1'))"); }); it('should generate valid URL for the Anomaly Detection job management page for groupIds', async () => { @@ -40,7 +40,9 @@ describe('MlUrlGenerator', () => { groupIds: ['farequote', 'categorization'], }, }); - expect(url).toBe("/app/ml/jobs?_a=(queryText:'groups:(farequote%20or%20categorization)')"); + expect(url).toBe( + "/app/ml/jobs?_a=(jobs:(queryText:'groups:(farequote%20or%20categorization)'))" + ); }); it('should generate valid URL for the page for selecting the type of anomaly detection job to create', async () => { @@ -180,7 +182,9 @@ describe('MlUrlGenerator', () => { jobId: 'grid_regression_1', }, }); - expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(jobId:grid_regression_1)'); + expect(url).toBe( + "/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:'id:grid_regression_1'))" + ); }); it('should generate valid URL for the Data Frame Analytics job management page with groupIds', async () => { @@ -190,7 +194,9 @@ describe('MlUrlGenerator', () => { groupIds: ['group_1', 'group_2'], }, }); - expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(groupIds:!(group_1,group_2))'); + expect(url).toBe( + "/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:'groups:(group_1%20or%20group_2)'))" + ); }); }); diff --git a/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap index 08a8cc57618a..ffb1620b60fa 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap @@ -14,7 +14,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = ` style="max-width:600px" >
cloud dashboard. + + + (opens in a new tab or window) + For more information on Monitoring in Elastic Cloud, please see the documentation. + + + (opens in a new tab or window) +

diff --git a/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap index e7b88e23c5f6..9e3b7c0e25d5 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap @@ -185,6 +185,16 @@ Array [ target="_blank" > cloud dashboard. + + + (opens in a new tab or window) + For more information on Monitoring in Elastic Cloud, please see the documentation. + + + (opens in a new tab or window) +

diff --git a/x-pack/plugins/monitoring/public/components/page_loading/__tests__/__snapshots__/page_loading.test.js.snap b/x-pack/plugins/monitoring/public/components/page_loading/__tests__/__snapshots__/page_loading.test.js.snap index f4d8232b2e34..fa223a2fe57e 100644 --- a/x-pack/plugins/monitoring/public/components/page_loading/__tests__/__snapshots__/page_loading.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/page_loading/__tests__/__snapshots__/page_loading.test.js.snap @@ -9,7 +9,7 @@ exports[`PageLoading should show a simple page loading component 1`] = ` class="euiPageBody" >
docs + + + + + + + (opens in a new tab or window) + + + to enable API keys. @@ -119,7 +144,7 @@ exports[`APIKeysGridPage renders permission denied if user does not have require paddingSize="l" >
renders permission denied if required 1`] = ` paddingSize="l" >
MockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"MockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security_solution/common/shared_exports.ts b/x-pack/plugins/security_solution/common/shared_exports.ts index bee2e54d0e3e..fb457933f4b5 100644 --- a/x-pack/plugins/security_solution/common/shared_exports.ts +++ b/x-pack/plugins/security_solution/common/shared_exports.ts @@ -13,7 +13,7 @@ export { DefaultVersionNumberDecoded, } from './detection_engine/schemas/types/default_version_number'; export { exactCheck } from './exact_check'; -export { getPaths, foldLeftRight } from './test_utils'; +export { getPaths, foldLeftRight, removeExternalLinkText } from './test_utils'; export { validate, validateEither } from './validate'; export { formatErrors } from './format_errors'; export { migratePackagePolicyToV7110 } from './endpoint/policy/migrations/to_v7_11.0'; diff --git a/x-pack/plugins/security_solution/common/test_utils.ts b/x-pack/plugins/security_solution/common/test_utils.ts index b96639ad7b03..2cdce67f364d 100644 --- a/x-pack/plugins/security_solution/common/test_utils.ts +++ b/x-pack/plugins/security_solution/common/test_utils.ts @@ -41,3 +41,9 @@ export const getPaths = (validation: t.Validation): string[] => { ) ); }; + +/** + * Convenience utility to remove text appended to links by EUI + */ +export const removeExternalLinkText = (str: string) => + str.replace(/\(opens in a new tab or window\)/g, ''); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 783f8be840b7..fb1f2920aace 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -49,6 +49,7 @@ import { DEFINITION_DETAILS, FALSE_POSITIVES_DETAILS, getDetails, + removeExternalLinkText, INDEX_PATTERNS_DETAILS, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, @@ -174,9 +175,13 @@ describe('Custom detection rules creation', () => { cy.get(ABOUT_DETAILS).within(() => { getDetails(SEVERITY_DETAILS).should('have.text', newRule.severity); getDetails(RISK_SCORE_DETAILS).should('have.text', newRule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should('have.text', expectedUrls); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index 3d4aaca8bb78..22d2a144932b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -32,6 +32,7 @@ import { DEFINITION_DETAILS, FALSE_POSITIVES_DETAILS, getDetails, + removeExternalLinkText, INDEX_PATTERNS_DETAILS, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, @@ -136,9 +137,13 @@ describe('Detection rules, EQL', () => { cy.get(ABOUT_DETAILS).within(() => { getDetails(SEVERITY_DETAILS).should('have.text', eqlRule.severity); getDetails(RISK_SCORE_DETAILS).should('have.text', eqlRule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should('have.text', expectedUrls); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts index 153c55fae59f..061b66faca05 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts @@ -23,6 +23,7 @@ import { DEFINITION_DETAILS, FALSE_POSITIVES_DETAILS, getDetails, + removeExternalLinkText, MACHINE_LEARNING_JOB_ID, MACHINE_LEARNING_JOB_STATUS, MITRE_ATTACK_DETAILS, @@ -122,9 +123,13 @@ describe('Detection rules, machine learning', () => { cy.get(ABOUT_DETAILS).within(() => { getDetails(SEVERITY_DETAILS).should('have.text', machineLearningRule.severity); getDetails(RISK_SCORE_DETAILS).should('have.text', machineLearningRule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should('have.text', expectedUrls); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); cy.get(DEFINITION_DETAILS).within(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index e905365d1bbb..b1ccca5e4f13 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -34,6 +34,7 @@ import { DETAILS_TITLE, FALSE_POSITIVES_DETAILS, getDetails, + removeExternalLinkText, INDEX_PATTERNS_DETAILS, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, @@ -141,9 +142,13 @@ describe('Detection rules, override', () => { `${newOverrideRule.riskOverride}signal.rule.risk_score` ); getDetails(RULE_NAME_OVERRIDE_DETAILS).should('have.text', newOverrideRule.nameOverride); - getDetails(REFERENCE_URLS_DETAILS).should('have.text', expectedUrls); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); getDetails(TAGS_DETAILS).should('have.text', expectedTags); getDetails(TIMESTAMP_OVERRIDE_DETAILS).should('have.text', newOverrideRule.timestampOverride); cy.contains(DETAILS_TITLE, 'Severity override') diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index a9b43d82bb7f..c3e7892d6327 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -32,6 +32,7 @@ import { FALSE_POSITIVES_DETAILS, DEFINITION_DETAILS, getDetails, + removeExternalLinkText, INDEX_PATTERNS_DETAILS, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, @@ -135,9 +136,13 @@ describe('Detection rules, threshold', () => { cy.get(ABOUT_DETAILS).within(() => { getDetails(SEVERITY_DETAILS).should('have.text', newThresholdRule.severity); getDetails(RISK_SCORE_DETAILS).should('have.text', newThresholdRule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should('have.text', expectedUrls); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index d72210dd3e08..8cf0dfb5f666 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -75,3 +75,6 @@ export const TIMESTAMP_OVERRIDE_DETAILS = 'Timestamp override'; export const getDetails = (title: string) => cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION); + +export const removeExternalLinkText = (str: string) => + str.replace(/\(opens in a new tab or window\)/g, ''); diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx index 2ae5b1d20c3c..ed7ec77b4f39 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx @@ -6,6 +6,7 @@ import { mount, shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { mountWithIntl } from '@kbn/test/jest'; import { encodeIpv6 } from '../../lib/helpers'; @@ -92,7 +93,7 @@ describe('Custom Links', () => { const wrapper = mountWithIntl( {'Example Link'} ); - expect(wrapper.text()).toEqual('Example Link'); + expect(removeExternalLinkText(wrapper.text())).toEqual('Example Link'); }); test('it renders props passed in as link', () => { @@ -448,7 +449,7 @@ describe('Custom Links', () => { describe('WhoisLink', () => { test('it renders ip passed in as domain', () => { const wrapper = mountWithIntl({'Example Link'}); - expect(wrapper.text()).toEqual('Example Link'); + expect(removeExternalLinkText(wrapper.text())).toEqual('Example Link'); }); test('it renders correct href', () => { @@ -473,7 +474,7 @@ describe('Custom Links', () => { {'Example Link'} ); - expect(wrapper.text()).toEqual('Example Link'); + expect(removeExternalLinkText(wrapper.text())).toEqual('Example Link'); }); test('it renders correct href', () => { @@ -504,7 +505,7 @@ describe('Custom Links', () => { const wrapper = mountWithIntl( {'Example Link'} ); - expect(wrapper.text()).toEqual('Example Link'); + expect(removeExternalLinkText(wrapper.text())).toEqual('Example Link'); }); test('it renders correct href', () => { @@ -533,7 +534,7 @@ describe('Custom Links', () => { const wrapper = mountWithIntl( {'Example Link'} ); - expect(wrapper.text()).toEqual('Example Link'); + expect(removeExternalLinkText(wrapper.text())).toEqual('Example Link'); }); test('it renders correct href when port is a number', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.test.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.test.tsx index e6a38863d7e5..8bb652816a97 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { MarkdownRenderer } from './renderer'; describe('Markdown', () => { @@ -16,9 +17,9 @@ describe('Markdown', () => { test('it renders the expected link text', () => { const wrapper = mount({markdownWithLink}); - expect(wrapper.find('[data-test-subj="markdown-link"]').first().text()).toEqual( - 'External Site' - ); + expect( + removeExternalLinkText(wrapper.find('[data-test-subj="markdown-link"]').first().text()) + ).toEqual('External Site'); }); test('it renders the expected href', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx index b0965f870855..90ab5c2f888f 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx @@ -55,7 +55,9 @@ describe('JobsTableComponent', () => { '[data-test-subj="jobs-table-link"]' ); await waitFor(() => - expect(href).toEqual('/app/ml/jobs?_a=(queryText:linux_anomalous_network_activity_ecs)') + expect(href).toEqual( + "/app/ml/jobs?_a=(jobs:(queryText:'id:linux_anomalous_network_activity_ecs'))" + ) ); }); @@ -72,7 +74,7 @@ describe('JobsTableComponent', () => { '[data-test-subj="jobs-table-link"]' ); await waitFor(() => - expect(href).toEqual("/app/ml/jobs?_a=(queryText:'job%20id%20with%20spaces')") + expect(href).toEqual("/app/ml/jobs?_a=(jobs:(queryText:'id:job%20id%20with%20spaces'))") ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap index 6331a2e02b21..6d114258224d 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -39,6 +39,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiBorderColor": "#343741", "euiBorderEditable": "2px dotted #343741", "euiBorderRadius": "4px", + "euiBorderRadiusSmall": "2px", "euiBorderThick": "2px solid #343741", "euiBorderThin": "1px solid #343741", "euiBorderWidthThick": "2px", @@ -375,6 +376,15 @@ exports[`Paginated Table Component rendering it renders the default load more ta }, }, "euiPaletteColorBlindKeys": "'euiColorVis0', 'euiColorVis1', 'euiColorVis2', 'euiColorVis3', 'euiColorVis4', 'euiColorVis5', 'euiColorVis6', 'euiColorVis7', 'euiColorVis8', 'euiColorVis9'", + "euiPanelBackgroundColorModifiers": Object { + "plain": "#1d1e24", + "subdued": "#25262e", + "transparent": "rgba(0, 0, 0, 0)", + }, + "euiPanelBorderRadiusModifiers": Object { + "borderRadiusMedium": "4px", + "borderRadiusNone": 0, + }, "euiPanelPaddingModifiers": Object { "paddingLarge": "24px", "paddingMedium": "16px", diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index 6d45059099f8..71b103949a80 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -121,7 +121,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiFlexItem euiFlexItem--flexGrowZero" >
{ ); - expect(wrapper.find('[data-test-subj="port"]').first().text()).toEqual('443'); + expect(removeExternalLinkText(wrapper.find('[data-test-subj="port"]').first().text())).toEqual( + '443' + ); }); test('it hyperlinks links destination.port to an external service that describes the purpose of the port', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx index 2ba5cd868c2b..2b0231999e6e 100644 --- a/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/source_destination/index.test.tsx @@ -9,6 +9,7 @@ import { shallow } from 'enzyme'; import { get } from 'lodash/fp'; import React from 'react'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { asArrayIfExists } from '../../../common/lib/helpers'; import { getMockNetflowData } from '../../../common/mock'; import '../../../common/mock/match_media'; @@ -189,9 +190,11 @@ describe('SourceDestination', () => { test('it renders the destination ip and port, separated with a colon', () => { const wrapper = mount({getSourceDestinationInstance()}); - expect(wrapper.find('[data-test-subj="destination-ip-and-port"]').first().text()).toEqual( - '10.1.2.3:80' - ); + expect( + removeExternalLinkText( + wrapper.find('[data-test-subj="destination-ip-and-port"]').first().text() + ) + ).toEqual('10.1.2.3:80'); }); test('it renders destination.packets', () => { @@ -313,9 +316,9 @@ describe('SourceDestination', () => { test('it renders the source ip and port, separated with a colon', () => { const wrapper = mount({getSourceDestinationInstance()}); - expect(wrapper.find('[data-test-subj="source-ip-and-port"]').first().text()).toEqual( - '192.168.1.2:9987' - ); + expect( + removeExternalLinkText(wrapper.find('[data-test-subj="source-ip-and-port"]').first().text()) + ).toEqual('192.168.1.2:9987'); }); test('it renders source.packets', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx index 890add422250..78f71a84d0b1 100644 --- a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_ip.test.tsx @@ -7,6 +7,7 @@ import { get } from 'lodash/fp'; import React from 'react'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { asArrayIfExists } from '../../../common/lib/helpers'; import { getMockNetflowData } from '../../../common/mock'; import '../../../common/mock/match_media'; @@ -976,9 +977,11 @@ describe('SourceDestinationIp', () => { ); - expect(wrapper.find('[data-test-subj="draggable-content-source.port"]').first().text()).toEqual( - '9987' - ); + expect( + removeExternalLinkText( + wrapper.find('[data-test-subj="draggable-content-source.port"]').first().text() + ) + ).toEqual('9987'); }); test('it renders the expected destination port when type is `destination`, and both destinationIp and destinationPort are populated', () => { @@ -1028,7 +1031,9 @@ describe('SourceDestinationIp', () => { ); expect( - wrapper.find('[data-test-subj="draggable-content-destination.port"]').first().text() + removeExternalLinkText( + wrapper.find('[data-test-subj="draggable-content-destination.port"]').first().text() + ) ).toEqual('80'); }); @@ -1078,9 +1083,11 @@ describe('SourceDestinationIp', () => { ); - expect(wrapper.find('[data-test-subj="draggable-content-source.port"]').first().text()).toEqual( - '9987' - ); + expect( + removeExternalLinkText( + wrapper.find('[data-test-subj="draggable-content-source.port"]').first().text() + ) + ).toEqual('9987'); }); test('it renders the expected destination port when type is `destination`, and only destinationPort is populated', () => { @@ -1131,7 +1138,9 @@ describe('SourceDestinationIp', () => { ); expect( - wrapper.find('[data-test-subj="draggable-content-destination.port"]').first().text() + removeExternalLinkText( + wrapper.find('[data-test-subj="draggable-content-destination.port"]').first().text() + ) ).toEqual('80'); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx index b31094b07a82..ef515751b634 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { TestProviders } from '../../../common/mock'; import '../../../common/mock/match_media'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; @@ -43,9 +44,11 @@ describe('CertificateFingerprint', () => { /> ); - expect(wrapper.find('[data-test-subj="certificate-fingerprint-link"]').first().text()).toEqual( - '3f4c57934e089f02ae7511200aee2d7e7aabd272' - ); + expect( + removeExternalLinkText( + wrapper.find('[data-test-subj="certificate-fingerprint-link"]').first().text() + ) + ).toEqual('3f4c57934e089f02ae7511200aee2d7e7aabd272'); }); test('it renders a hyperlink to an external site to compare the fingerprint against a known set of signatures', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx index 899a6d7486f9..c57546d5cd9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { TestProviders } from '../../../common/mock'; import '../../../common/mock/match_media'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; @@ -42,9 +43,9 @@ describe('Ja3Fingerprint', () => { ); - expect(wrapper.find('[data-test-subj="ja3-fingerprint-link"]').first().text()).toEqual( - 'fff799d91b7c01ae3fe6787cfc895552' - ); + expect( + removeExternalLinkText(wrapper.find('[data-test-subj="ja3-fingerprint-link"]').first().text()) + ).toEqual('fff799d91b7c01ae3fe6787cfc895552'); }); test('it renders a hyperlink to an external site to compare the fingerprint against a known set of signatures', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx index c2026a71ac6f..9aa462ee23a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx @@ -8,6 +8,7 @@ import { get } from 'lodash/fp'; import React from 'react'; import { shallow } from 'enzyme'; +import { removeExternalLinkText } from '../../../../common/test_utils'; import { asArrayIfExists } from '../../../common/lib/helpers'; import { getMockNetflowData } from '../../../common/mock'; import '../../../common/mock/match_media'; @@ -188,9 +189,11 @@ describe('Netflow', () => { test('it renders the destination ip and port, separated with a colon', () => { const wrapper = mount({getNetflowInstance()}); - expect(wrapper.find('[data-test-subj="destination-ip-and-port"]').first().text()).toEqual( - '10.1.2.3:80' - ); + expect( + removeExternalLinkText( + wrapper.find('[data-test-subj="destination-ip-and-port"]').first().text() + ) + ).toEqual('10.1.2.3:80'); }); test('it renders destination.packets', () => { @@ -324,9 +327,9 @@ describe('Netflow', () => { test('it renders the source ip and port, separated with a colon', () => { const wrapper = mount({getNetflowInstance()}); - expect(wrapper.find('[data-test-subj="source-ip-and-port"]').first().text()).toEqual( - '192.168.1.2:9987' - ); + expect( + removeExternalLinkText(wrapper.find('[data-test-subj="source-ip-and-port"]').first().text()) + ).toEqual('192.168.1.2:9987'); }); test('it renders source.packets', () => { @@ -353,11 +356,13 @@ describe('Netflow', () => { const wrapper = mount({getNetflowInstance()}); expect( - wrapper - .find('[data-test-subj="client-certificate-fingerprint"]') - .find('[data-test-subj="certificate-fingerprint-link"]') - .first() - .text() + removeExternalLinkText( + wrapper + .find('[data-test-subj="client-certificate-fingerprint"]') + .find('[data-test-subj="certificate-fingerprint-link"]') + .first() + .text() + ) ).toEqual('tls.client_certificate.fingerprint.sha1-value'); }); @@ -372,9 +377,9 @@ describe('Netflow', () => { test('renders tls.fingerprints.ja3.hash text', () => { const wrapper = mount({getNetflowInstance()}); - expect(wrapper.find('[data-test-subj="ja3-fingerprint-link"]').first().text()).toEqual( - 'tls.fingerprints.ja3.hash-value' - ); + expect( + removeExternalLinkText(wrapper.find('[data-test-subj="ja3-fingerprint-link"]').first().text()) + ).toEqual('tls.fingerprints.ja3.hash-value'); }); test('it hyperlinks tls.server_certificate.fingerprint.sha1 site to compare the fingerprint against a known set of signatures', () => { @@ -395,11 +400,13 @@ describe('Netflow', () => { const wrapper = mount({getNetflowInstance()}); expect( - wrapper - .find('[data-test-subj="server-certificate-fingerprint"]') - .find('[data-test-subj="certificate-fingerprint-link"]') - .first() - .text() + removeExternalLinkText( + wrapper + .find('[data-test-subj="server-certificate-fingerprint"]') + .find('[data-test-subj="certificate-fingerprint-link"]') + .first() + .text() + ) ).toEqual('tls.server_certificate.fingerprint.sha1-value'); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap index 17c614bd2c83..03dc2afc625c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap @@ -39,6 +39,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiBorderColor": "#343741", "euiBorderEditable": "2px dotted #343741", "euiBorderRadius": "4px", + "euiBorderRadiusSmall": "2px", "euiBorderThick": "2px solid #343741", "euiBorderThin": "1px solid #343741", "euiBorderWidthThick": "2px", @@ -375,6 +376,15 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` }, }, "euiPaletteColorBlindKeys": "'euiColorVis0', 'euiColorVis1', 'euiColorVis2', 'euiColorVis3', 'euiColorVis4', 'euiColorVis5', 'euiColorVis6', 'euiColorVis7', 'euiColorVis8', 'euiColorVis9'", + "euiPanelBackgroundColorModifiers": Object { + "plain": "#1d1e24", + "subdued": "#25262e", + "transparent": "rgba(0, 0, 0, 0)", + }, + "euiPanelBorderRadiusModifiers": Object { + "borderRadiusMedium": "4px", + "borderRadiusNone": 0, + }, "euiPanelPaddingModifiers": Object { "paddingLarge": "24px", "paddingMedium": "16px", diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx index 52b9b9a31fc9..bb61821d3131 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash'; import React from 'react'; +import { removeExternalLinkText } from '../../../../../../common/test_utils'; import '../../../../../common/mock/match_media'; import { mockBrowserFields } from '../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../common/ecs'; @@ -75,7 +76,7 @@ describe('get_column_renderer', () => { {row} ); - expect(wrapper.text()).toContain( + expect(removeExternalLinkText(wrapper.text())).toContain( '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); @@ -93,7 +94,7 @@ describe('get_column_renderer', () => { {row} ); - expect(wrapper.text()).toContain( + expect(removeExternalLinkText(wrapper.text())).toContain( '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); @@ -111,7 +112,7 @@ describe('get_column_renderer', () => { {row} ); - expect(wrapper.text()).toContain( + expect(removeExternalLinkText(wrapper.text())).toContain( 'C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx index 3b9752224e2c..7ef034994ce5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { removeExternalLinkText } from '../../../../../../../common/test_utils'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData } from '../../../../../../common/mock'; import '../../../../../../common/mock/match_media'; @@ -41,7 +42,7 @@ describe('SuricataDetails', () => { /> ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx index bed217171538..674f922bf54e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import { removeExternalLinkText } from '../../../../../../../common/test_utils'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../../common/ecs'; import { mockTimelineData } from '../../../../../../common/mock'; @@ -58,7 +59,7 @@ describe('suricata_row_renderer', () => { {children} ); - expect(wrapper.text()).toContain( + expect(removeExternalLinkText(wrapper.text())).toContain( '4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx index 6c2e9ad7535a..45381bbc9993 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import { removeExternalLinkText } from '../../../../../../../common/test_utils'; import { BrowserFields } from '../../../../../../common/containers/source'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../../common/ecs'; @@ -541,7 +542,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'SYSTEM\\NT AUTHORITY@HD-gqf-0af7b4feaccepted a connection viaAmSvc.exe(1084)tcp1:network-community_idSource127.0.0.1:49306Destination127.0.0.1:49305' ); }); @@ -569,7 +570,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'SYSTEM\\NT AUTHORITY@HD-55b-3ec87f66accepted a connection via(4)tcp1:network-community_idSource::1:51324Destination::1:5357' ); }); @@ -597,7 +598,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'Arun\\Anvi-Acer@HD-obe-8bf77f54disconnected viachrome.exe(11620)8.1KBtcp1:LxYHJJv98b2O0fNccXu6HheXmwk=Source192.168.0.6:59356(25.78%)2.1KB(74.22%)6KBDestination10.156.162.53:443' ); }); @@ -625,7 +626,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'SYSTEM\\NT AUTHORITY@HD-55b-3ec87f66disconnected via(4)7.9KBtcp1:ZylzQhsB1dcptA2t4DY8S6l9o8E=Source::1:51338(96.92%)7.7KB(3.08%)249BDestination::1:2869' ); }); @@ -653,7 +654,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'root@foohostopened a socket withgoogle_accounts(2166)Outbound socket (10.4.20.1:59554 -> 10.1.2.3:80) Ooutboundtcp1:network-community_idSource10.4.20.1:59554Destination10.1.2.3:80' ); }); @@ -681,7 +682,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'root@foohostclosed a socket withgoogle_accounts(2166)Outbound socket (10.4.20.1:59508 -> 10.1.2.3:80) Coutboundtcp1:network-community_idSource10.4.20.1:59508Destination10.1.2.3:80' ); }); @@ -875,7 +876,7 @@ describe('GenericRowRenderer', () => { ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'iot.example.comasked forlookup.example.comwith question typeA, which resolved to10.1.2.3(response code:NOERROR)viaan unknown process6.937500msOct 8, 2019 @ 10:05:23.241Oct 8, 2019 @ 10:05:23.248outbounddns177Budp1:network-community_idSource10.9.9.9:58732(22.60%)40B(77.40%)137BDestination10.1.1.1:53OceaniaAustralia🇦🇺AU' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx index 434be7b23aee..b980f723b5c0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import { removeExternalLinkText } from '../../../../../../../common/test_utils'; import '../../../../../../common/mock/match_media'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; @@ -41,7 +42,7 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' ); }); @@ -56,7 +57,7 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'CyIrMA1L1JtLqdIuoldnsudpSource206.189.35.240:57475Destination67.207.67.3:53' ); }); @@ -71,7 +72,7 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'CZLkpC22NquQJOpkwehttp302Source206.189.35.240:36220Destination192.241.164.26:80' ); }); @@ -86,7 +87,7 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'noticeDropped:falseScan::Port_Scan8.42.77.171 scanned at least 15 unique ports of host 207.154.238.205 in 0m0sSource8.42.77.171' ); }); @@ -101,7 +102,7 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.text()).toEqual( + expect(removeExternalLinkText(wrapper.text())).toEqual( 'CmTxzt2OVXZLkGDaResslTLSv12TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256Source188.166.66.184:34514Destination91.189.95.15:443' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx index 9c08fbacafcd..043bde1c1069 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import { removeExternalLinkText } from '../../../../../../../common/test_utils'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../../common/ecs'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; @@ -57,7 +58,7 @@ describe('zeek_row_renderer', () => { {children} ); - expect(wrapper.text()).toContain( + expect(removeExternalLinkText(wrapper.text())).toContain( 'C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx index efa22cb2c561..f148ac5420b0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import { removeExternalLinkText } from '../../../../../../../common/test_utils'; import '../../../../../../common/mock/match_media'; import { Ecs } from '../../../../../../../common/ecs'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; @@ -89,7 +90,7 @@ describe('ZeekSignature', () => { test('should render value', () => { const wrapper = mount(); - expect(wrapper.text()).toEqual('abc'); + expect(removeExternalLinkText(wrapper.text())).toEqual('abc'); }); test('should render value and link', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx index a7de73c9aab2..a51765fc3a72 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -89,10 +89,12 @@ describe('health check', () => { const [description, action] = queryAllByText(/TLS/i); expect(description.textContent).toMatchInlineSnapshot( - `"Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. Learn how to enable TLS."` + `"Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. Learn how to enable TLS.(opens in a new tab or window)"` ); - expect(action.textContent).toMatchInlineSnapshot(`"Learn how to enable TLS."`); + expect(action.textContent).toMatchInlineSnapshot( + `"Learn how to enable TLS.(opens in a new tab or window)"` + ); expect(action.getAttribute('href')).toMatchInlineSnapshot( `"elastic.co/guide/en/kibana/current/configuring-tls.html"` @@ -118,11 +120,11 @@ describe('health check', () => { const description = queryByRole(/banner/i); expect(description!.textContent).toMatchInlineSnapshot( - `"To create an alert, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file. Learn how."` + `"To create an alert, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file. Learn how.(opens in a new tab or window)"` ); const action = queryByText(/Learn/i); - expect(action!.textContent).toMatchInlineSnapshot(`"Learn how."`); + expect(action!.textContent).toMatchInlineSnapshot(`"Learn how.(opens in a new tab or window)"`); expect(action!.getAttribute('href')).toMatchInlineSnapshot( `"elastic.co/guide/en/kibana/current/alert-action-settings-kb.html#general-alert-action-settings"` ); @@ -148,11 +150,11 @@ describe('health check', () => { const description = queryByText(/Transport Layer Security/i); expect(description!.textContent).toMatchInlineSnapshot( - `"You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. Learn how"` + `"You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. Learn how(opens in a new tab or window)"` ); const action = queryByText(/Learn/i); - expect(action!.textContent).toMatchInlineSnapshot(`"Learn how"`); + expect(action!.textContent).toMatchInlineSnapshot(`"Learn how(opens in a new tab or window)"`); expect(action!.getAttribute('href')).toMatchInlineSnapshot( `"elastic.co/guide/en/kibana/current/alerting-getting-started.html#alerting-setup-prerequisites"` ); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap index 137d11d3f3b0..8ace0445d0eb 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap @@ -178,6 +178,16 @@ exports[`PingListExpandedRow renders link to docs if body is not recorded but it + + + (opens in a new tab or window) + for more information on recording response bodies.
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap index 69c6bf3fadb1..7cc96a42411d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap @@ -55,6 +55,16 @@ Array [ + + + (opens in a new tab or window) +
{ // First, make sure the mapping actually includes a runtime field - const fieldMapping = (await es.indices.getFieldMapping({ - index: 'flstest', - fields: '*', - includeDefaults: true, - })) as FLSFieldMappingResponse; + const mapping = (await es.indices.getMapping({ index: 'flstest' })) as FLSMappingResponse; - expect(Object.keys(fieldMapping.flstest.mappings)).to.contain('runtime_customer_ssn'); - expect( - fieldMapping.flstest.mappings.runtime_customer_ssn.mapping.runtime_customer_ssn.type - ).to.eql('runtime'); + expect(Object.keys(mapping.flstest.mappings)).to.contain('runtime'); + expect(Object.keys(mapping.flstest.mappings.runtime!)).to.contain('runtime_customer_ssn'); // Now, make sure it's not returned here const { body: actualFields } = (await supertest diff --git a/x-pack/test/functional/es_archives/logstash_functional/mappings.json b/x-pack/test/functional/es_archives/logstash_functional/mappings.json index ee7feedd7753..12853523615b 100644 --- a/x-pack/test/functional/es_archives/logstash_functional/mappings.json +++ b/x-pack/test/functional/es_archives/logstash_functional/mappings.json @@ -19,6 +19,12 @@ } } ], + "runtime": { + "runtime_number": { + "type": "long", + "script" : { "source" : "emit(doc['bytes'].value)" } + } + }, "properties": { "@message": { "fields": { @@ -342,11 +348,6 @@ } }, "type": "text" - }, - "runtime_number": { - "type": "runtime", - "runtime_type" : "long", - "script" : { "source" : "emit(doc['bytes'].value)" } } } }, @@ -389,6 +390,12 @@ } } ], + "runtime": { + "runtime_number": { + "type": "long", + "script" : { "source" : "emit(doc['bytes'].value)" } + } + }, "properties": { "@message": { "fields": { @@ -712,11 +719,6 @@ } }, "type": "text" - }, - "runtime_number": { - "type": "runtime", - "runtime_type" : "long", - "script" : { "source" : "emit(doc['bytes'].value)" } } } }, @@ -759,6 +761,12 @@ } } ], + "runtime": { + "runtime_number": { + "type": "long", + "script" : { "source" : "emit(doc['bytes'].value)" } + } + }, "properties": { "@message": { "fields": { @@ -1082,11 +1090,6 @@ } }, "type": "text" - }, - "runtime_number": { - "type": "runtime", - "runtime_type" : "long", - "script" : { "source" : "emit(doc['bytes'].value)" } } } }, @@ -1106,4 +1109,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/es_archives/security/flstest/data/mappings.json b/x-pack/test/functional/es_archives/security/flstest/data/mappings.json index 3605533618a9..4f419e4b6ade 100644 --- a/x-pack/test/functional/es_archives/security/flstest/data/mappings.json +++ b/x-pack/test/functional/es_archives/security/flstest/data/mappings.json @@ -3,6 +3,14 @@ "value": { "index": "flstest", "mappings": { + "runtime": { + "runtime_customer_ssn": { + "type": "keyword", + "script": { + "source": "emit(doc['customer_ssn'].value + ' calculated at runtime')" + } + } + }, "properties": { "customer_name": { "fields": { @@ -30,13 +38,6 @@ } }, "type": "text" - }, - "runtime_customer_ssn": { - "type": "runtime", - "runtime_type": "keyword", - "script": { - "source": "emit(doc['customer_ssn'].value + ' calculated at runtime')" - } } } }, @@ -47,4 +48,4 @@ } } } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 91ae4b236adf..f2b4147fc3c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1433,10 +1433,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-30.1.1.tgz#7d3a4e8241cca2e27078f9fa6a0138b26acd3db3" - integrity sha512-nsqrOFJfPIGS+TTd4W4viesWdPEDCxazfrPc+DZQ4kosRXLKoJ4lLKDuJKbg90HshCeamKSFgEiqWlqKAlyV+A== +"@elastic/eui@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-30.2.0.tgz#17bc630eb1a03e05d453b9934568cfbf3a82b736" + integrity sha512-VUmy7Qz49kN8a3f58bilfMwBo9zCJvHyBOUdDA1O77jFYv3+Hg7e/+uGb7Q1k6kSmCfv4r99eZWrcxVroYU4nQ== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160"