diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap
deleted file mode 100644
index 163bad28d8cc2..0000000000000
--- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap
+++ /dev/null
@@ -1,1083 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`CustomUrlEditor renders the editor for a dashboard type URL with a label 1`] = `
-
-
-
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
-`;
-
-exports[`CustomUrlEditor renders the editor for a discover type URL with an entity and empty time range interval 1`] = `
-
-
-
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
-`;
-
-exports[`CustomUrlEditor renders the editor for a discover type URL with valid time range interval 1`] = `
-
-
-
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
-`;
-
-exports[`CustomUrlEditor renders the editor for a new dashboard type URL with no label 1`] = `
-
-
-
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-
-
-`;
-
-exports[`CustomUrlEditor renders the editor for other type of URL with duplicate label 1`] = `
-
-
-
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-`;
-
-exports[`CustomUrlEditor renders the editor for other type of URL with unique label 1`] = `
-
-
-
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
- }
- labelType="label"
- >
-
-
-
-
-`;
diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx
deleted file mode 100644
index ce7f31df4e86c..0000000000000
--- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-// Mock the mlJobService that is used for testing custom URLs.
-import { shallow } from 'enzyme';
-
-jest.mock('../../../services/job_service', () => 'mlJobService');
-
-import React from 'react';
-
-import { CustomUrlEditor } from './editor';
-import { TIME_RANGE_TYPE, URL_TYPE } from './constants';
-import { CustomUrlSettings } from './utils';
-import { DataViewListItem } from '@kbn/data-views-plugin/common';
-
-function prepareTest(
- customUrl: CustomUrlSettings,
- setEditCustomUrlFn: (url: CustomUrlSettings) => void
-) {
- const savedCustomUrls = [
- {
- url_name: 'Show data',
- time_range: 'auto',
- url_value:
- "discover#/?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=" +
- '(index:e532ba80-b76f-11e8-a9dc-37914a458883,query:(language:lucene,query:\'airline:"$airline$"\'))',
- },
- {
- url_name: 'Show dashboard',
- time_range: '1h',
- url_value:
- 'dashboards#/view/52ea8840-bbef-11e8-a04d-b1701b2b977e?_g=' +
- "(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&" +
- '_a=(filters:!(),query:(language:lucene,query:\'airline:"$airline$"\'))',
- },
- {
- url_name: 'Show airline',
- time_range: 'auto',
- url_value: 'http://airlinecodes.info/airline-code-$airline$',
- },
- ];
-
- const dashboards = [
- { id: 'dash1', title: 'Dashboard 1' },
- { id: 'dash2', title: 'Dashboard 2' },
- ];
-
- const dataViewListItems = [
- { id: 'pattern1', title: 'Data view 1' },
- { id: 'pattern2', title: 'Data view 2' },
- ] as DataViewListItem[];
-
- const queryEntityFieldNames = ['airline'];
-
- const props = {
- customUrl,
- setEditCustomUrl: setEditCustomUrlFn,
- savedCustomUrls,
- dashboards,
- dataViewListItems,
- queryEntityFieldNames,
- };
-
- return shallow();
-}
-
-describe('CustomUrlEditor', () => {
- const setEditCustomUrl = jest.fn(() => {});
- const dashboardUrl = {
- label: '',
- timeRange: {
- type: TIME_RANGE_TYPE.AUTO,
- interval: '',
- },
- type: URL_TYPE.KIBANA_DASHBOARD,
- kibanaSettings: {
- queryFieldNames: [],
- dashboardId: 'dash1',
- },
- };
-
- const discoverUrl = {
- label: 'Open Discover',
- timeRange: {
- type: TIME_RANGE_TYPE.INTERVAL,
- interval: '',
- },
- type: URL_TYPE.KIBANA_DISCOVER,
- kibanaSettings: {
- queryFieldNames: ['airline'],
- discoverIndexPatternId: 'pattern1',
- },
- };
-
- const otherUrl = {
- label: 'Show airline',
- timeRange: {
- type: TIME_RANGE_TYPE.AUTO,
- interval: '',
- },
- type: URL_TYPE.OTHER,
- otherUrlSettings: {
- urlValue: 'https://www.google.co.uk/search?q=airline+code+$airline$',
- },
- };
-
- test('renders the editor for a new dashboard type URL with no label', () => {
- const wrapper = prepareTest(dashboardUrl, setEditCustomUrl);
- expect(wrapper).toMatchSnapshot();
- });
-
- test('renders the editor for a dashboard type URL with a label', () => {
- const dashboardUrlEdit = {
- ...dashboardUrl,
- label: 'Open Dashboard 1',
- };
- const wrapper = prepareTest(dashboardUrlEdit, setEditCustomUrl);
- expect(wrapper).toMatchSnapshot();
- });
-
- test('renders the editor for a discover type URL with an entity and empty time range interval', () => {
- const wrapper = prepareTest(discoverUrl, setEditCustomUrl);
- expect(wrapper).toMatchSnapshot();
- });
-
- test('renders the editor for a discover type URL with valid time range interval', () => {
- const discoverUrlEdit = {
- ...discoverUrl,
- timeRange: {
- type: TIME_RANGE_TYPE.INTERVAL,
- interval: '1h',
- },
- };
- const wrapper = prepareTest(discoverUrlEdit, setEditCustomUrl);
- expect(wrapper).toMatchSnapshot();
- });
-
- test('renders the editor for other type of URL with duplicate label', () => {
- const wrapper = prepareTest(otherUrl, setEditCustomUrl);
- expect(wrapper).toMatchSnapshot();
- });
-
- test('renders the editor for other type of URL with unique label', () => {
- const otherUrlEdit = {
- ...otherUrl,
- label: 'View airline',
- };
- const wrapper = prepareTest(otherUrlEdit, setEditCustomUrl);
- expect(wrapper).toMatchSnapshot();
- });
-
- test('calls setEditCustomUrl on updating a custom URL field', () => {
- const wrapper = prepareTest(dashboardUrl, setEditCustomUrl);
- const labelInput = wrapper.find('EuiFieldText').first();
- labelInput.simulate('change', { target: { value: 'Edit' } });
- wrapper.update();
- expect(setEditCustomUrl).toHaveBeenCalled();
- });
-});
diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx
index 9eddc02b5e1a4..315c60fab6a6f 100644
--- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx
+++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { ChangeEvent, FC } from 'react';
+import React, { ChangeEvent, useMemo, useState, useRef, useEffect, FC } from 'react';
import {
EuiComboBox,
@@ -25,11 +25,16 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { DataViewListItem } from '@kbn/data-views-plugin/common';
+import { DataView } from '@kbn/data-views-plugin/public';
import { CustomUrlSettings, isValidCustomUrlSettingsTimeRange } from './utils';
import { isValidLabel } from '../../../util/custom_url_utils';
+import { type DataFrameAnalyticsConfig } from '../../../../../common/types/data_frame_analytics';
+import { Job, isAnomalyDetectionJob } from '../../../../../common/types/anomaly_detection_jobs';
import { TIME_RANGE_TYPE, TimeRangeType, URL_TYPE } from './constants';
import { UrlConfig } from '../../../../../common/types/custom_urls';
+import { useMlKibana } from '../../../contexts/kibana';
+import { getDropDownOptions } from './get_dropdown_options';
function getLinkToOptions() {
return [
@@ -60,8 +65,8 @@ interface CustomUrlEditorProps {
savedCustomUrls: UrlConfig[];
dashboards: Array<{ id: string; title: string }>;
dataViewListItems: DataViewListItem[];
- queryEntityFieldNames: string[];
showTimeRangeSelector?: boolean;
+ job: Job | DataFrameAnalyticsConfig;
}
/*
@@ -73,9 +78,42 @@ export const CustomUrlEditor: FC = ({
savedCustomUrls,
dashboards,
dataViewListItems,
- queryEntityFieldNames,
- showTimeRangeSelector = true,
+ job,
}) => {
+ const [queryEntityFieldNames, setQueryEntityFieldNames] = useState([]);
+ const isAnomalyJob = useMemo(() => isAnomalyDetectionJob(job), [job]);
+
+ const {
+ services: {
+ data: { dataViews },
+ },
+ } = useMlKibana();
+
+ const isFirst = useRef(true);
+
+ useEffect(() => {
+ async function getQueryEntityDropdownOptions() {
+ let dataViewToUse: DataView | undefined;
+ const dataViewId = customUrl?.kibanaSettings?.discoverIndexPatternId;
+
+ try {
+ dataViewToUse = await dataViews.get(dataViewId ?? '');
+ } catch (e) {
+ dataViewToUse = undefined;
+ }
+ const dropDownOptions = await getDropDownOptions(isFirst.current, job, dataViewToUse);
+ setQueryEntityFieldNames(dropDownOptions);
+
+ if (isFirst.current) {
+ isFirst.current = false;
+ }
+ }
+
+ if (job !== undefined) {
+ getQueryEntityDropdownOptions();
+ }
+ }, [dataViews, job, customUrl?.kibanaSettings?.discoverIndexPatternId]);
+
if (customUrl === undefined) {
return null;
}
@@ -112,6 +150,7 @@ export const CustomUrlEditor: FC = ({
kibanaSettings: {
...kibanaSettings,
discoverIndexPatternId: e.target.value,
+ queryFieldNames: [],
},
});
};
@@ -307,58 +346,57 @@ export const CustomUrlEditor: FC = ({
)}
- {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) &&
- showTimeRangeSelector && (
- <>
-
-
-
+ {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && isAnomalyJob && (
+ <>
+
+
+
+
+ }
+ className="url-time-range"
+ display="rowCompressed"
+ >
+
+
+
+ {timeRange.type === TIME_RANGE_TYPE.INTERVAL && (
+
}
className="url-time-range"
+ error={invalidIntervalError}
+ isInvalid={isInvalidTimeRange}
display="rowCompressed"
>
-
- {timeRange.type === TIME_RANGE_TYPE.INTERVAL && (
-
-
- }
- className="url-time-range"
- error={invalidIntervalError}
- isInvalid={isInvalidTimeRange}
- display="rowCompressed"
- >
-
-
-
- )}
-
- >
- )}
+ )}
+
+ >
+ )}
{type === URL_TYPE.OTHER && (
0) {
indicesName = job.dest.index;
+ backupIndicesName = job.source.index[0];
query = job.source?.query ?? {};
jobId = job.id;
}
const defaultDataViewId = dataViews.find((dv) => dv.title === indicesName)?.id;
- kibanaSettings.discoverIndexPatternId = defaultDataViewId;
+ if (defaultDataViewId === undefined && backupIndicesName !== undefined) {
+ backupDataViewId = dataViews.find((dv) => dv.title === backupIndicesName)?.id;
+ }
+ kibanaSettings.discoverIndexPatternId = defaultDataViewId ?? backupDataViewId ?? '';
kibanaSettings.filters =
defaultDataViewId === null ? [] : getFiltersForDSLQuery(query, defaultDataViewId, jobId);
@@ -134,17 +141,23 @@ export function getNewCustomUrlDefaults(
// Returns the list of supported field names that can be used
// to add to the query used when linking to a Kibana dashboard or Discover.
export function getSupportedFieldNames(
- job: DataFrameAnalyticsConfig,
+ job: DataFrameAnalyticsConfig | Job,
dataView: DataView
): string[] {
- const resultsField = job.dest.results_field;
const sortedFields = dataView.fields.getAll().sort((a, b) => a.name.localeCompare(b.name)) ?? [];
- const categoryFields = sortedFields.filter(
- (f) =>
+ let filterFunction: (field: DataViewField) => boolean = (field: DataViewField) =>
+ categoryFieldTypes.some((type) => {
+ return field.esTypes?.includes(type);
+ });
+
+ if (isDataFrameAnalyticsConfigs(job)) {
+ const resultsField = job.dest.results_field;
+ filterFunction = (f) =>
categoryFieldTypes.some((type) => {
return f.esTypes?.includes(type);
- }) && !f.name.startsWith(resultsField ?? DEFAULT_RESULTS_FIELD)
- );
+ }) && !f.name.startsWith(resultsField ?? DEFAULT_RESULTS_FIELD);
+ }
+ const categoryFields = sortedFields.filter(filterFunction);
return categoryFields.map((field) => field.name);
}
diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx
index 7ebab7b3a1359..9d3db04fa40de 100644
--- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx
+++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx
@@ -22,7 +22,6 @@ import {
EuiModalFooter,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
-import { DataView } from '@kbn/data-views-plugin/public';
import { i18n } from '@kbn/i18n';
import { withKibana } from '@kbn/kibana-react-plugin/public';
@@ -31,8 +30,6 @@ import { MlKibanaReactContextValue } from '../../contexts/kibana';
import { CustomUrlEditor, CustomUrlList } from './custom_url_editor';
import {
getNewCustomUrlDefaults,
- getQueryEntityFieldNames,
- getSupportedFieldNames,
isValidCustomUrlSettings,
buildCustomUrlFromSettings,
getTestUrl,
@@ -43,22 +40,8 @@ import {
loadDataViewListItems,
} from '../../jobs/jobs_list/components/edit_job_flyout/edit_utils';
import { openCustomUrlWindow } from '../../util/custom_url_utils';
-import { Job, isAnomalyDetectionJob } from '../../../../common/types/anomaly_detection_jobs';
import { UrlConfig } from '../../../../common/types/custom_urls';
import type { CustomUrlsWrapperProps } from './custom_urls_wrapper';
-import {
- isDataFrameAnalyticsConfigs,
- type DataFrameAnalyticsConfig,
-} from '../../../../common/types/data_frame_analytics';
-
-function getDropDownOptions(job: Job | DataFrameAnalyticsConfig, dataView?: DataView) {
- if (isAnomalyDetectionJob(job)) {
- return getQueryEntityFieldNames(job);
- } else if (isDataFrameAnalyticsConfigs(job) && dataView !== undefined) {
- return getSupportedFieldNames(job, dataView);
- }
- return [];
-}
const MAX_NUMBER_DASHBOARDS = 1000;
@@ -66,14 +49,12 @@ interface CustomUrlsState {
customUrls: UrlConfig[];
dashboards: Array<{ id: string; title: string }>;
dataViewListItems: DataViewListItem[];
- queryEntityFieldNames: string[];
editorOpen: boolean;
editorSettings?: CustomUrlSettings;
supportedFilterFields: string[];
}
interface CustomUrlsProps extends CustomUrlsWrapperProps {
kibana: MlKibanaReactContextValue;
- dataView?: DataView;
currentTimeFilter?: EsQueryTimeRange;
}
@@ -85,7 +66,6 @@ class CustomUrlsUI extends Component {
customUrls: [],
dashboards: [],
dataViewListItems: [],
- queryEntityFieldNames: [],
editorOpen: false,
supportedFilterFields: [],
};
@@ -95,8 +75,6 @@ class CustomUrlsUI extends Component {
return {
job: props.job,
customUrls: props.jobCustomUrls,
- // For DFA uses the destination index Data View to get the query entities and falls back to source index Data View.
- queryEntityFieldNames: getDropDownOptions(props.job, props.dataView),
};
}
@@ -223,25 +201,17 @@ class CustomUrlsUI extends Component {
};
renderEditor() {
- const {
- customUrls,
- editorOpen,
- editorSettings,
- dashboards,
- dataViewListItems,
- queryEntityFieldNames,
- } = this.state;
+ const { customUrls, editorOpen, editorSettings, dashboards, dataViewListItems } = this.state;
const editMode = this.props.editMode ?? 'inline';
const editor = (
);
diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx
index a4a125d7cb52f..175b16dca74ea 100644
--- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx
+++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx
@@ -5,16 +5,11 @@
* 2.0.
*/
-import React, { useEffect, useState, FC } from 'react';
-import { DataView } from '@kbn/data-views-plugin/public';
+import React, { FC } from 'react';
import { useMlKibana } from '../../contexts/kibana';
import { Job } from '../../../../common/types/anomaly_detection_jobs';
import { UrlConfig } from '../../../../common/types/custom_urls';
-import { getDataViewIdFromName } from '../../util/index_utils';
-import {
- isDataFrameAnalyticsConfigs,
- type DataFrameAnalyticsConfig,
-} from '../../../../common/types/data_frame_analytics';
+import { type DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics';
import { CustomUrls } from './custom_urls';
export interface CustomUrlsWrapperProps {
@@ -25,12 +20,9 @@ export interface CustomUrlsWrapperProps {
}
export const CustomUrlsWrapper: FC = (props) => {
- const [dataView, setDataView] = useState();
-
const {
services: {
data: {
- dataViews,
query: {
timefilter: { timefilter },
},
@@ -38,40 +30,5 @@ export const CustomUrlsWrapper: FC = (props) => {
},
} = useMlKibana();
- useEffect(() => {
- let active = true;
-
- async function loadDataView() {
- if (isDataFrameAnalyticsConfigs(props.job)) {
- const destIndex = props.job.dest.index;
- const sourceIndex = props.job.source.index[0];
- let dataViewIdSource: string | null;
- let dataViewIdDest: string | null;
- let dv: DataView | undefined;
-
- try {
- dataViewIdSource = await getDataViewIdFromName(sourceIndex);
- dataViewIdDest = await getDataViewIdFromName(destIndex);
- dv = await dataViews.get(dataViewIdDest ?? dataViewIdSource ?? '');
-
- if (dv === undefined) {
- dv = await dataViews.get(dataViewIdSource ?? '');
- }
- if (!active) return;
- setDataView(dv);
- } catch (e) {
- dv = undefined;
- }
-
- return dv;
- }
- }
-
- loadDataView();
- return () => {
- active = false;
- };
- }, [dataViews, props.job]);
-
- return ;
+ return ;
};