* fix: 🐛 cast type

* chore: 🤖 remove unused parameter

* feat: 🎸 implement ML locator

* test: 💍 add locator tests

* feat: 🎸 expose ML locator form plugin contract

* feat: 🎸 deprecate ml url generator

* feat: 🎸 use locator in useMlHref() React hook

* fix: 🐛 remove non-existing property

* fix: 🐛 remove unused parameter

* feat: 🎸 replace url generator by locator

* refactor: 💡 remove ML url generator and replace by locator

* fix: 🐛 correct type check error

* test: 💍 add share plugin mock and use it

* test: 💍 update mock

* Remove usage of excludeBasePath

* Fix recently accessed url for create job to data visualizer

* refactor: 💡 rename interface

* test: 💍 move locator mock into the share plugin

* test: 💍 update Jest snapshot

* test: 💍 use shared URL service mock

* refactor: 💡 update usage after merging latest

* refactor: 💡 use locator instead of generator

* chore: 🤖 remove unused import

Co-authored-by: Quynh Nguyen <quynh.nguyen@elastic.co>
This commit is contained in:
Vadim Dalecky 2021-07-08 16:24:12 +02:00 committed by GitHub
parent 0ef3cbe6ed
commit 15d47ec2c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
92 changed files with 974 additions and 883 deletions

View file

@ -16,7 +16,7 @@ export class MockUrlService extends UrlService {
super({
navigate: async () => {},
getUrl: async ({ app, path }, { absolute }) => {
return `${absolute ? 'https://example.com' : ''}/app/${app}${path}`;
return `${absolute ? 'http://localhost:8888' : ''}/app/${app}${path}`;
},
});
}

View file

@ -6,4 +6,59 @@
* Side Public License, v 1.
*/
import { SerializableState } from 'src/plugins/kibana_utils/common';
import { SharePluginSetup, SharePluginStart } from '.';
import { LocatorPublic, UrlService } from '../common/url_service';
export type Setup = jest.Mocked<SharePluginSetup>;
export type Start = jest.Mocked<SharePluginStart>;
const url = new UrlService({
navigate: async () => {},
getUrl: async ({ app, path }, { absolute }) => {
return `${absolute ? 'http://localhost:8888' : ''}/app/${app}${path}`;
},
});
const createSetupContract = (): Setup => {
const setupContract: Setup = {
register: jest.fn(),
urlGenerators: {
registerUrlGenerator: jest.fn(),
},
url,
};
return setupContract;
};
const createStartContract = (): Start => {
const startContract: Start = {
url,
urlGenerators: {
getUrlGenerator: jest.fn(),
},
toggleShareContextMenu: jest.fn(),
};
return startContract;
};
const createLocator = <T extends SerializableState = SerializableState>(): jest.Mocked<
LocatorPublic<T>
> => ({
getLocation: jest.fn(),
getUrl: jest.fn(),
useUrl: jest.fn(),
navigate: jest.fn(),
extract: jest.fn(),
inject: jest.fn(),
telemetry: jest.fn(),
migrations: {},
});
export const sharePluginMock = {
createSetupContract,
createStartContract,
createLocator,
};
export * from '../common/mocks';

View file

@ -7,12 +7,13 @@
import React, { ReactNode } from 'react';
import { Observable, of } from 'rxjs';
import { UrlService } from '../../../../../../src/plugins/share/common/url_service';
import { createObservabilityRuleTypeRegistryMock } from '../../../../observability/public';
import { ApmPluginContext, ApmPluginContextValue } from './apm_plugin_context';
import { ConfigSchema } from '../..';
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
import { createCallApmApi } from '../../services/rest/createCallApmApi';
import { MlUrlGenerator } from '../../../../ml/public';
import { MlLocatorDefinition } from '../../../../ml/public';
const uiSettings: Record<string, unknown> = {
[UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [
@ -86,12 +87,17 @@ const mockConfig: ConfigSchema = {
profilingEnabled: false,
};
const urlService = new UrlService({
navigate: async () => {},
getUrl: async ({ app, path }, { absolute }) => {
return `${absolute ? 'http://localhost:8888' : ''}/app/${app}${path}`;
},
});
const locator = urlService.locators.create(new MlLocatorDefinition());
const mockPlugin = {
ml: {
urlGenerator: new MlUrlGenerator({
appBasePath: '/app/ml',
useHash: false,
}),
locator,
},
data: {
query: {

View file

@ -39,7 +39,7 @@ export const LogAnalysisModuleListCard: React.FC<{
const [viewInMlLink, setViewInMlLink] = useState<string>('');
const getMlUrl = async () => {
if (!ml.urlGenerator) {
if (!ml.locator) {
toasts.addWarning({
title: mountReactNode(
<FormattedMessage
@ -50,7 +50,7 @@ export const LogAnalysisModuleListCard: React.FC<{
});
return;
}
setViewInMlLink(await ml.urlGenerator.createUrl({ page: 'jobs', pageState: { jobId } }));
setViewInMlLink(await ml.locator.getUrl({ page: 'jobs', pageState: { jobId } }));
};
useEffect(() => {

View file

@ -5,7 +5,7 @@
* 2.0.
*/
export const ML_APP_URL_GENERATOR = 'ML_APP_URL_GENERATOR';
export const ML_APP_LOCATOR = 'ML_APP_LOCATOR';
export const ML_PAGES = {
ANOMALY_DETECTION_JOBS_MANAGE: 'jobs',

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { MlPages } from '../constants/ml_url_generator';
import { MlPages } from '../constants/locator';
export interface Dictionary<TValue> {
[id: string]: TValue;

View file

@ -5,21 +5,23 @@
* 2.0.
*/
import type { SerializableState } from 'src/plugins/kibana_utils/common';
import type { LocatorPublic } from 'src/plugins/share/public';
import type { RefreshInterval, TimeRange } from '../../../../../src/plugins/data/common/query';
import type { JobId } from './anomaly_detection_jobs/job';
import { ML_PAGES } from '../constants/ml_url_generator';
import type { DataFrameAnalysisConfigType } from './data_frame_analytics';
import type { SearchQueryLanguage } from '../constants/search';
import type { ListingPageUrlState } from './common';
import type { InfluencersFilterQuery } from './es_client';
import { ML_PAGES } from '../constants/locator';
type OptionalPageState = object | undefined;
export type MLPageState<PageType, PageState> = PageState extends OptionalPageState
? { page: PageType; pageState?: PageState; excludeBasePath?: boolean }
? { page: PageType; pageState?: PageState }
: PageState extends object
? { page: PageType; pageState: PageState; excludeBasePath?: boolean }
: { page: PageType; excludeBasePath?: boolean };
? { page: PageType; pageState: PageState }
: { page: PageType };
export interface MlCommonGlobalState {
time?: TimeRange;
@ -241,7 +243,7 @@ export type ExplorationPageUrlState = {
/**
* Union type of ML URL state based on page
*/
export type MlUrlGeneratorState =
export type MlLocatorState =
| AnomalyDetectionUrlState
| ExplorerUrlState
| TimeSeriesExplorerUrlState
@ -250,3 +252,7 @@ export type MlUrlGeneratorState =
| CalendarEditUrlState
| FilterEditUrlState
| MlGenericUrlState;
export type MlLocatorParams = MlLocatorState & SerializableState;
export type MlLocator = LocatorPublic<MlLocatorParams>;

View file

@ -8,14 +8,13 @@
import { uiActionsPluginMock } from '../../../../../src/plugins/ui_actions/public/mocks';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
import { kibanaLegacyPluginMock } from '../../../../../src/plugins/kibana_legacy/public/mocks';
import { sharePluginMock } from '../../../../../src/plugins/share/public/mocks';
import { embeddablePluginMock } from '../../../../../src/plugins/embeddable/public/mocks';
import { triggersActionsUiMock } from '../../../triggers_actions_ui/public/mocks';
export const createMlStartDepsMock = () => ({
data: dataPluginMock.createStartContract(),
share: {
urlGenerators: { getUrlGenerator: jest.fn() },
},
share: sharePluginMock.createStartContract(),
kibanaLegacy: kibanaLegacyPluginMock.createStartContract(),
uiActions: uiActionsPluginMock.createStartContract(),
spaces: jest.fn(),

View file

@ -12,7 +12,7 @@ import type { MlAnomalyDetectionAlertParams } from '../../common/types/alerts';
import type { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public';
import type { PluginSetupContract as AlertingSetup } from '../../../alerting/public';
import { PLUGIN_ID } from '../../common/constants/app';
import { createExplorerUrl } from '../ml_url_generator/anomaly_detection_urls_generator';
import { formatExplorerUrl } from '../locator/formatters/anomaly_detection';
import { validateLookbackInterval, validateTopNBucket } from './validators';
export function registerMlAlerts(
@ -152,6 +152,6 @@ export function registerNavigation(alerting: AlertingSetup) {
]),
];
return createExplorerUrl('', { jobIds });
return formatExplorerUrl('', { jobIds });
});
}

View file

@ -24,7 +24,7 @@ import { MlSetupDependencies, MlStartDependencies } from '../plugin';
import { MlRouter } from './routing';
import { mlApiServicesProvider } from './services/ml_api_service';
import { HttpService } from './services/http_service';
import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../common/constants/ml_url_generator';
import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator';
export type MlDependencies = Omit<MlSetupDependencies, 'share' | 'indexPatternManagement'> &
MlStartDependencies;
@ -55,11 +55,9 @@ export type MlGlobalServices = ReturnType<typeof getMlGlobalServices>;
const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
const redirectToMlAccessDeniedPage = async () => {
const accessDeniedPageUrl = await deps.share.urlGenerators
.getUrlGenerator(ML_APP_URL_GENERATOR)
.createUrl({
page: ML_PAGES.ACCESS_DENIED,
});
const accessDeniedPageUrl = await deps.share.url.locators.get(ML_APP_LOCATOR)!.getUrl({
page: ML_PAGES.ACCESS_DENIED,
});
await coreStart.application.navigateToUrl(accessDeniedPageUrl);
};

View file

@ -47,8 +47,7 @@ import {
ANNOTATION_EVENT_DELAYED_DATA,
} from '../../../../../common/constants/annotations';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { PLUGIN_ID } from '../../../../../common/constants/app';
import { ML_APP_LOCATOR, ML_PAGES } from '../../../../../common/constants/locator';
import { timeFormatter } from '../../../../../common/util/date_utils';
import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context';
import { DatafeedChartFlyout } from '../../../jobs/jobs_list/components/datafeed_chart_flyout';
@ -202,11 +201,8 @@ class AnnotationsTableUI extends Component {
openSingleMetricView = async (annotation = {}) => {
const {
services: {
application: { navigateToApp },
share: {
urlGenerators: { getUrlGenerator },
},
application: { navigateToUrl },
share,
},
} = this.props.kibana;
@ -266,32 +262,32 @@ class AnnotationsTableUI extends Component {
mlTimeSeriesExplorer.entities = entityCondition;
// appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer;
const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
const singleMetricViewerLink = await mlUrlGenerator.createUrl({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
timeRange,
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
jobIds: [job.job_id],
query: {
query_string: {
analyze_wildcard: true,
query: '*',
const mlLocator = share.url.locators.get(ML_APP_LOCATOR);
const singleMetricViewerLink = await mlLocator.getUrl(
{
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
timeRange,
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
jobIds: [job.job_id],
query: {
query_string: {
analyze_wildcard: true,
query: '*',
},
},
...mlTimeSeriesExplorer,
},
...mlTimeSeriesExplorer,
},
excludeBasePath: true,
});
{ absolute: true }
);
addItemToRecentlyAccessed('timeseriesexplorer', job.job_id, singleMetricViewerLink);
await navigateToApp(PLUGIN_ID, {
path: singleMetricViewerLink,
});
await navigateToUrl(singleMetricViewerLink);
};
onMouseOverRow = (record) => {

View file

@ -30,7 +30,7 @@ import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_util
import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils';
import { getIndexPatternIdFromName } from '../../util/index_utils';
import { replaceStringTokens } from '../../util/string_utils';
import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { ML_APP_LOCATOR, ML_PAGES } from '../../../../common/constants/locator';
/*
* Component for rendering the links menu inside a cell in the anomalies table.
*/
@ -146,13 +146,9 @@ class LinksMenuUI extends Component {
viewSeries = async () => {
const {
services: {
share: {
urlGenerators: { getUrlGenerator },
},
},
services: { share },
} = this.props.kibana;
const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
const mlLocator = share.url.locators.get(ML_APP_LOCATOR);
const record = this.props.anomaly.source;
const bounds = this.props.bounds;
@ -182,33 +178,35 @@ class LinksMenuUI extends Component {
entityCondition[record.by_field_name] = record.by_field_value;
}
const singleMetricViewerLink = await mlUrlGenerator.createUrl({
excludeBasePath: false,
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: [record.job_id],
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
timeRange: {
from: from,
to: to,
mode: 'absolute',
},
zoom: {
from: zoomFrom,
to: zoomTo,
},
detectorIndex: record.detector_index,
entities: entityCondition,
query_string: {
analyze_wildcard: true,
query: '*',
const singleMetricViewerLink = await mlLocator.getUrl(
{
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: [record.job_id],
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
timeRange: {
from: from,
to: to,
mode: 'absolute',
},
zoom: {
from: zoomFrom,
to: zoomTo,
},
detectorIndex: record.detector_index,
entities: entityCondition,
query_string: {
analyze_wildcard: true,
query: '*',
},
},
},
});
{ absolute: true }
);
window.open(singleMetricViewerLink, '_blank');
};

View file

@ -16,9 +16,9 @@ import { AnomalyResultsViewSelector } from './index';
jest.mock('../../contexts/kibana', () => {
return {
useMlUrlGenerator: () => ({
createUrl: jest.fn(),
}),
useMlLocator: () =>
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('../../../../../../../src/plugins/share/public/mocks').sharePluginMock.createLocator(),
useNavigateToPath: () => jest.fn(),
};
});

View file

@ -12,8 +12,8 @@ import { EuiButtonGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useUrlState } from '../../util/url_state';
import { useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { useMlLocator, useNavigateToPath } from '../../contexts/kibana';
import { ML_PAGES } from '../../../../common/constants/locator';
interface Props {
viewId: typeof ML_PAGES.SINGLE_METRIC_VIEWER | typeof ML_PAGES.ANOMALY_EXPLORER;
@ -22,7 +22,7 @@ interface Props {
// Component for rendering a set of buttons for switching between the Anomaly Detection results views.
export const AnomalyResultsViewSelector: FC<Props> = ({ viewId }) => {
const urlGenerator = useMlUrlGenerator();
const locator = useMlLocator()!;
const navigateToPath = useNavigateToPath();
const toggleButtonsIcons = useMemo(
@ -52,7 +52,7 @@ export const AnomalyResultsViewSelector: FC<Props> = ({ viewId }) => {
const [globalState] = useUrlState('_g');
const onChangeView = async (newViewId: Props['viewId']) => {
const url = await urlGenerator.createUrl({
const url = await locator.getUrl({
page: newViewId,
pageState: {
globalState,

View file

@ -24,7 +24,7 @@ import {
import { LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services';
import { i18n } from '@kbn/i18n';
import { useMlKibana } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { PLUGIN_ID } from '../../../../../common/constants/app';
const JOB_FILTER_FIELDS = ['job_id', 'groups'];

View file

@ -10,8 +10,8 @@ import React, { FC, useState, useEffect } from 'react';
import { EuiPageHeader } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TabId } from './navigation_menu';
import { useMlKibana, useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
import { MlUrlGeneratorState } from '../../../../common/types/ml_url_generator';
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../contexts/kibana';
import { MlLocatorParams } from '../../../../common/types/locator';
import { useUrlState } from '../../util/url_state';
import { ML_APP_NAME } from '../../../../common/constants/app';
import './main_tabs.scss';
@ -68,7 +68,7 @@ function getTabs(disableLinks: boolean): Tab[] {
}
interface TabData {
testSubject: string;
pathId?: MlUrlGeneratorState['page'];
pathId?: MlLocatorParams['page'];
name: string;
}
@ -126,10 +126,10 @@ export const MainTabs: FC<Props> = ({ tabId, disableLinks }) => {
}
const tabs = getTabs(disableLinks);
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
const navigateToPath = useNavigateToPath();
const redirectToTab = async (defaultPathId: MlUrlGeneratorState['page']) => {
const redirectToTab = async (defaultPathId: MlLocatorParams['page']) => {
const pageState =
globalState?.refreshInterval !== undefined
? {
@ -140,7 +140,7 @@ export const MainTabs: FC<Props> = ({ tabId, disableLinks }) => {
: undefined;
// TODO - Fix ts so passing pageState won't default to MlGenericUrlState when pageState is passed in
// @ts-ignore
const path = await mlUrlGenerator.createUrl({
const path = await mlLocator.getUrl({
page: defaultPathId,
// only retain the refreshInterval part of globalState
// appState will not be considered.
@ -162,7 +162,7 @@ export const MainTabs: FC<Props> = ({ tabId, disableLinks }) => {
tabs={tabs.map((tab: Tab) => {
const { id, disabled } = tab;
const testSubject = TAB_DATA[id].testSubject;
const defaultPathId = (TAB_DATA[id].pathId || id) as MlUrlGeneratorState['page'];
const defaultPathId = (TAB_DATA[id].pathId || id) as MlLocatorParams['page'];
return {
label: tab.name,

View file

@ -18,7 +18,7 @@ import { ScopeExpression } from './scope_expression';
import { checkPermission } from '../../capabilities/check_capabilities';
import { getScopeFieldDefaults } from './utils';
import { FormattedMessage } from '@kbn/i18n/react';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../common/constants/locator';
import { useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
function NoFilterListsCallOut() {

View file

@ -10,5 +10,5 @@ export { useNavigateToPath, NavigateToPath } from './use_navigate_to_path';
export { useUiSettings } from './use_ui_settings_context';
export { useTimefilter } from './use_timefilter';
export { useNotifications } from './use_notifications_context';
export { useMlUrlGenerator, useMlLink } from './use_create_url';
export { useMlLocator, useMlLink } from './use_create_url';
export { useMlApiContext } from './use_ml_api_context';

View file

@ -7,47 +7,46 @@
import { useCallback, useEffect, useState } from 'react';
import { useMlKibana } from './kibana_context';
import { ML_APP_URL_GENERATOR } from '../../../../common/constants/ml_url_generator';
import { MlUrlGeneratorState } from '../../../../common/types/ml_url_generator';
import { ML_APP_LOCATOR } from '../../../../common/constants/locator';
import { MlLocatorParams } from '../../../../common/types/locator';
import { useUrlState } from '../../util/url_state';
import { LocatorGetUrlParams } from '../../../../../../../src/plugins/share/common/url_service';
export const useMlUrlGenerator = () => {
export const useMlLocator = () => {
const {
services: {
share: {
urlGenerators: { getUrlGenerator },
},
},
services: { share },
} = useMlKibana();
return getUrlGenerator(ML_APP_URL_GENERATOR);
return share.url.locators.get(ML_APP_LOCATOR);
};
export const useMlLink = (params: MlUrlGeneratorState): string => {
export const useMlLink = (params: MlLocatorParams, getUrlParams?: LocatorGetUrlParams): string => {
const [href, setHref] = useState<string>(params.page);
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
useEffect(() => {
let isCancelled = false;
const generateUrl = async (_params: MlUrlGeneratorState) => {
const url = await mlUrlGenerator.createUrl(_params);
if (!isCancelled) {
setHref(url);
const generateUrl = async (_params: MlLocatorParams) => {
if (mlLocator) {
const url = await mlLocator.getUrl(_params, getUrlParams);
if (!isCancelled) {
setHref(url);
}
}
};
generateUrl(params);
return () => {
isCancelled = true;
};
}, [params]);
}, [params, getUrlParams]);
return href;
};
export const useCreateAndNavigateToMlLink = (
page: MlUrlGeneratorState['page']
page: MlLocatorParams['page']
): (() => Promise<void>) => {
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
const [globalState] = useUrlState('_g');
const {
@ -57,7 +56,7 @@ export const useCreateAndNavigateToMlLink = (
} = useMlKibana();
const redirectToMlPage = useCallback(
async (_page: MlUrlGeneratorState['page']) => {
async (_page: MlLocatorParams['page']) => {
const pageState =
globalState?.refreshInterval !== undefined
? {
@ -69,10 +68,10 @@ export const useCreateAndNavigateToMlLink = (
// TODO: fix ts only interpreting it as MlUrlGenericState if pageState is passed
// @ts-ignore
const url = await mlUrlGenerator.createUrl({ page: _page, pageState });
const url = await mlLocator.getUrl({ page: _page, pageState });
await navigateToUrl(url);
},
[mlUrlGenerator, navigateToUrl]
[mlLocator, navigateToUrl]
);
// returns the onClick callback

View file

@ -9,7 +9,7 @@ import React, { FC, Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlLink } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
export const BackToListPanel: FC = () => {
const analyticsManagementPageLink = useMlLink({

View file

@ -9,7 +9,7 @@ import React, { FC, Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlLink } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
interface Props {
jobId: string;

View file

@ -22,7 +22,7 @@ import {
getDefaultExplorationPageUrlState,
useExplorationUrlState,
} from '../../hooks/use_exploration_url_state';
import { ExpandablePanels } from '../../../../../../../common/types/ml_url_generator';
import { ExpandablePanels } from '../../../../../../../common/types/locator';
interface HeaderItem {
// id is used as the React key and to construct a data-test-subj

View file

@ -6,9 +6,9 @@
*/
import { usePageUrlState } from '../../../../util/url_state';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { ExplorationPageUrlState } from '../../../../../../common/types/locator';
import { SEARCH_QUERY_LANGUAGE } from '../../../../../../common/constants/search';
import { ExplorationPageUrlState } from '../../../../../../common/types/ml_url_generator';
import { isPopulatedObject } from '../../../../../../common/util/object_utils';
export function getDefaultExplorationPageUrlState(

View file

@ -6,20 +6,20 @@
*/
import React, { useCallback, useMemo } from 'react';
import { useMlUrlGenerator, useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlLocator, useNavigateToPath } from '../../../../../contexts/kibana';
import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import { getViewLinkStatus } from '../action_view/get_view_link_status';
import { mapActionButtonText, MapButton } from './map_button';
export type MapAction = ReturnType<typeof useMapAction>;
export const useMapAction = () => {
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator()!;
const navigateToPath = useNavigateToPath();
const clickHandler = useCallback(async (item: DataFrameAnalyticsListRow) => {
const path = await mlUrlGenerator.createUrl({
const path = await mlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
pageState: { jobId: item.id },
});

View file

@ -8,22 +8,22 @@
import React, { useCallback, useMemo } from 'react';
import { getAnalysisType } from '../../../../common/analytics';
import { useMlUrlGenerator, useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlLocator, useNavigateToPath } from '../../../../../contexts/kibana';
import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common';
import { getViewLinkStatus } from './get_view_link_status';
import { viewActionButtonText, ViewButton } from './view_button';
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
export type ViewAction = ReturnType<typeof useViewAction>;
export const useViewAction = () => {
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator()!;
const navigateToPath = useNavigateToPath();
const redirectToTab = async (jobId: string, analysisType: DataFrameAnalysisConfigType) => {
const path = await mlUrlGenerator.createUrl({
const path = await mlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: { jobId, analysisType },
});

View file

@ -32,7 +32,7 @@ import {
} from './common';
import { useActions } from './use_actions';
import { useMlLink } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import type { SpacesPluginStart } from '../../../../../../../../spaces/public';
import { JobSpacesList } from '../../../../../components/job_spaces_list';

View file

@ -30,7 +30,7 @@ import { ModelsTableToConfigMapping } from './index';
import { DeleteModelsModal } from './delete_models_modal';
import {
useMlKibana,
useMlUrlGenerator,
useMlLocator,
useNavigateToPath,
useNotifications,
} from '../../../../../contexts/kibana';
@ -47,7 +47,7 @@ import {
refreshAnalyticsList$,
useRefreshAnalyticsList,
} from '../../../../common';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { timeFormatter } from '../../../../../../../common/util/date_utils';
import { ListingPageUrlState } from '../../../../../../../common/types/common';
@ -83,7 +83,7 @@ export const ModelsList: FC = () => {
application: { navigateToUrl, capabilities },
},
} = useMlKibana();
const urlGenerator = useMlUrlGenerator();
const urlLocator = useMlLocator()!;
const [pageState, updatePageState] = usePageUrlState(
ML_PAGES.DATA_FRAME_ANALYTICS_MODELS_MANAGE,
@ -105,7 +105,6 @@ export const ModelsList: FC = () => {
{}
);
const mlUrlGenerator = useMlUrlGenerator();
const navigateToPath = useNavigateToPath();
const isBuiltInModel = useCallback(
@ -300,7 +299,7 @@ export const ModelsList: FC = () => {
item.metadata?.analytics_config.analysis
) as DataFrameAnalysisConfigType;
const url = await urlGenerator.createUrl({
const url = await urlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId: item.metadata?.analytics_config.id as string,
@ -329,7 +328,7 @@ export const ModelsList: FC = () => {
isPrimary: true,
available: (item) => !!item.metadata?.analytics_config?.id,
onClick: async (item) => {
const path = await mlUrlGenerator.createUrl({
const path = await urlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
pageState: { modelId: item.model_id },
});

View file

@ -36,7 +36,7 @@ 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';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { HelpMenu } from '../../../components/help_menu';
import { useMlKibana } from '../../../contexts/kibana';

View file

@ -30,13 +30,9 @@ import { EuiDescriptionListProps } from '@elastic/eui/src/components/description
import { CytoscapeContext } from './cytoscape';
import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/util/date_utils';
import { JOB_MAP_NODE_TYPES } from '../../../../../../common/constants/data_frame_analytics';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import {
useMlUrlGenerator,
useNotifications,
useNavigateToPath,
} from '../../../../contexts/kibana';
import { useMlLocator, useNotifications, useNavigateToPath } from '../../../../contexts/kibana';
import { getIndexPatternIdFromName } from '../../../../util/index_utils';
import { useNavigateToWizardWithClonedJob } from '../../analytics_management/components/action_clone/clone_action_name';
import {
@ -98,7 +94,7 @@ export const Controls: FC<Props> = React.memo(
openDeleteJobCheckModal,
} = deleteAction;
const { toasts } = useNotifications();
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator()!;
const navigateToPath = useNavigateToPath();
const navigateToWizardWithClonedJob = useNavigateToWizardWithClonedJob();
@ -119,7 +115,7 @@ export const Controls: FC<Props> = React.memo(
const indexId = getIndexPatternIdFromName(nodeLabel);
if (indexId) {
const path = await mlUrlGenerator.createUrl({
const path = await mlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB,
pageState: { index: indexId },
});

View file

@ -10,9 +10,9 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { Cytoscape, Controls, JobMapLegend } from './components';
import { useMlKibana, useMlUrlGenerator } from '../../../contexts/kibana';
import { useMlKibana, useMlLocator } from '../../../contexts/kibana';
import { JOB_MAP_NODE_TYPES } from '../../../../../common/constants/data_frame_analytics';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useCurrentEuiTheme, EuiThemeType } from '../../../components/color_range_legend';
import { useRefDimensions } from './components/use_ref_dimensions';
import { useFetchAnalyticsMapData } from './use_fetch_analytics_map_data';
@ -64,11 +64,11 @@ export const JobMap: FC<Props> = ({ analyticsId, modelId }) => {
application: { navigateToUrl },
},
} = useMlKibana();
const urlGenerator = useMlUrlGenerator();
const locator = useMlLocator()!;
const { euiTheme } = useCurrentEuiTheme();
const redirectToAnalyticsManagementPage = async () => {
const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
const url = await locator.getUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
await navigateToUrl(url);
};

View file

@ -10,9 +10,9 @@ import { i18n } from '@kbn/i18n';
import { useTimefilter } from '../../contexts/kibana';
import { NavigationMenu } from '../../components/navigation_menu';
import { HelpMenu } from '../../components/help_menu';
import { useMlKibana, useMlUrlGenerator } from '../../contexts/kibana';
import { useMlKibana, useMlLocator } from '../../contexts/kibana';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../common/constants/locator';
import { isFullLicense } from '../../license';
import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
import { checkPermission } from '../../capabilities/check_capabilities';
@ -34,7 +34,7 @@ export const FileDataVisualizerPage: FC = () => {
},
},
} = useMlKibana();
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator()!;
getMlNodeCount();
const [FileDataVisualizer, setFileDataVisualizer] = useState<FileDataVisualizerSpec | null>(null);
@ -50,7 +50,7 @@ export const FileDataVisualizerPage: FC = () => {
icon: 'machineLearningApp',
type: 'file',
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
return await mlUrlGenerator.createUrl({
return await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
pageState: {
index: indexPatternId,
@ -81,7 +81,7 @@ export const FileDataVisualizerPage: FC = () => {
icon: 'dataVisualizer',
type: 'file',
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
return await mlUrlGenerator.createUrl({
return await mlLocator.getUrl({
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
pageState: {
index: indexPatternId,

View file

@ -7,10 +7,10 @@
import React, { FC, Fragment, useEffect, useState, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { useMlKibana, useTimefilter, useMlUrlGenerator } from '../../contexts/kibana';
import { useMlKibana, useTimefilter, useMlLocator } from '../../contexts/kibana';
import { NavigationMenu } from '../../components/navigation_menu';
import { HelpMenu } from '../../components/help_menu';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../common/constants/locator';
import { isFullLicense } from '../../license';
import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
import { checkPermission } from '../../capabilities/check_capabilities';
@ -33,7 +33,7 @@ export const IndexDataVisualizerPage: FC = () => {
},
},
} = useMlKibana();
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator()!;
getMlNodeCount();
const [IndexDataVisualizer, setIndexDataVisualizer] = useState<IndexDataVisualizerSpec | null>(
@ -64,7 +64,7 @@ export const IndexDataVisualizerPage: FC = () => {
icon: 'createAdvancedJob',
type: 'file',
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
return await mlUrlGenerator.createUrl({
return await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED,
pageState: {
index: indexPatternId,
@ -101,7 +101,7 @@ export const IndexDataVisualizerPage: FC = () => {
icon: 'classificationJob',
type: 'file',
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
return await mlUrlGenerator.createUrl({
return await mlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB,
pageState: {
index: indexPatternId,

View file

@ -3,20 +3,17 @@
exports[`ExplorerNoInfluencersFound snapshot 1`] = `
<EuiEmptyPrompt
actions={
<Link
to="/jobs"
<EuiButton
color="primary"
fill={true}
href="/jobs"
>
<EuiButton
color="primary"
fill={true}
>
<FormattedMessage
defaultMessage="Create job"
id="xpack.ml.explorer.createNewJobLinkText"
values={Object {}}
/>
</EuiButton>
</Link>
<FormattedMessage
defaultMessage="Create job"
id="xpack.ml.explorer.createNewJobLinkText"
values={Object {}}
/>
</EuiButton>
}
data-test-subj="mlNoJobsFound"
iconType="alert"

View file

@ -8,18 +8,19 @@
/*
* React component for rendering EuiEmptyPrompt when no jobs were found.
*/
import { Link } from 'react-router-dom';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useMlLink } from '../../../contexts/kibana/use_create_url';
export const ExplorerNoJobsFound = () => {
const ADJobsManagementUrl = useMlLink({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
excludeBasePath: true,
});
const ADJobsManagementUrl = useMlLink(
{
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
},
{ absolute: true }
);
return (
<EuiEmptyPrompt
iconType="alert"
@ -32,14 +33,12 @@ export const ExplorerNoJobsFound = () => {
</h2>
}
actions={
<Link to={ADJobsManagementUrl}>
<EuiButton color="primary" fill>
<FormattedMessage
id="xpack.ml.explorer.createNewJobLinkText"
defaultMessage="Create job"
/>
</EuiButton>
</Link>
<EuiButton color="primary" href={ADJobsManagementUrl} fill>
<FormattedMessage
id="xpack.ml.explorer.createNewJobLinkText"
defaultMessage="Create job"
/>
</EuiButton>
}
data-test-subj="mlNoJobsFound"
/>

View file

@ -73,7 +73,7 @@ import { AnomaliesMap } from './anomalies_map';
import { getToastNotifications } from '../util/dependency_cache';
import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/settings';
import { withKibana } from '../../../../../../src/plugins/kibana_react/public';
import { ML_APP_URL_GENERATOR } from '../../../common/constants/ml_url_generator';
import { ML_APP_LOCATOR } from '../../../common/constants/locator';
import { AnomalyContextMenu } from './anomaly_context_menu';
import { isDefined } from '../../../common/types/guards';
@ -230,13 +230,9 @@ export class ExplorerUI extends React.Component {
updateLanguage = (language) => this.setState({ language });
render() {
const {
share: {
urlGenerators: { getUrlGenerator },
},
} = this.props.kibana.services;
const { share } = this.props.kibana.services;
const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
const mlLocator = share.url.locators.get(ML_APP_LOCATOR);
const {
showCharts,
@ -502,7 +498,7 @@ export class ExplorerUI extends React.Component {
...chartsData,
severity,
timefilter,
mlUrlGenerator,
mlLocator,
timeBuckets,
onSelectEntity: this.applyFilter,
}}

View file

@ -15,11 +15,11 @@ import {
SelectSeverityUI,
TableSeverity,
} from '../../components/controls/select_severity/select_severity';
import type { UrlGeneratorContract } from '../../../../../../../src/plugins/share/public';
import type { TimeBuckets } from '../../util/time_buckets';
import type { TimefilterContract } from '../../../../../../../src/plugins/data/public';
import type { EntityFieldOperation } from '../../../../common/util/anomaly_utils';
import type { ExplorerChartsData } from './explorer_charts_container_service';
import type { MlLocator } from '../../../../common/types/locator';
interface ExplorerAnomaliesContainerProps {
id: string;
@ -27,7 +27,7 @@ interface ExplorerAnomaliesContainerProps {
showCharts: boolean;
severity: TableSeverity;
setSeverity: (severity: TableSeverity) => void;
mlUrlGenerator: UrlGeneratorContract<'ML_APP_URL_GENERATOR'>;
mlLocator: MlLocator;
timeBuckets: TimeBuckets;
timefilter: TimefilterContract;
onSelectEntity: (fieldName: string, fieldValue: string, operation: EntityFieldOperation) => void;
@ -48,7 +48,7 @@ export const ExplorerAnomaliesContainer: FC<ExplorerAnomaliesContainerProps> = (
showCharts,
severity,
setSeverity,
mlUrlGenerator,
mlLocator,
timeBuckets,
timefilter,
onSelectEntity,
@ -81,7 +81,7 @@ export const ExplorerAnomaliesContainer: FC<ExplorerAnomaliesContainerProps> = (
{...{
...chartsData,
severity: severity.val,
mlUrlGenerator,
mlLocator,
timeBuckets,
timefilter,
onSelectEntity,

View file

@ -65,8 +65,7 @@ function ExplorerChartContainer({
severity,
tooManyBuckets,
wrapLabel,
mlUrlGenerator,
basePath,
mlLocator,
timeBuckets,
timefilter,
onSelectEntity,
@ -80,11 +79,7 @@ function ExplorerChartContainer({
let isCancelled = false;
const generateLink = async () => {
if (!isCancelled && series.functionDescription !== ML_JOB_AGGREGATION.LAT_LONG) {
const singleMetricViewerLink = await getExploreSeriesLink(
mlUrlGenerator,
series,
timefilter
);
const singleMetricViewerLink = await getExploreSeriesLink(mlLocator, series, timefilter);
setExplorerSeriesLink(singleMetricViewerLink);
}
};
@ -92,7 +87,7 @@ function ExplorerChartContainer({
return () => {
isCancelled = true;
};
}, [mlUrlGenerator, series]);
}, [mlLocator, series]);
const addToRecentlyAccessed = useCallback(() => {
if (recentlyAccessed) {
@ -163,7 +158,7 @@ function ExplorerChartContainer({
iconSide="right"
iconType="visLine"
size="xs"
href={`${basePath}/app/ml${explorerSeriesLink}`}
href={explorerSeriesLink}
onClick={addToRecentlyAccessed}
>
<FormattedMessage id="xpack.ml.explorer.charts.viewLabel" defaultMessage="View" />
@ -235,7 +230,7 @@ export const ExplorerChartsContainerUI = ({
tooManyBuckets,
kibana,
errorMessages,
mlUrlGenerator,
mlLocator,
timeBuckets,
timefilter,
onSelectEntity,
@ -245,7 +240,6 @@ export const ExplorerChartsContainerUI = ({
const {
services: {
chrome: { recentlyAccessed },
http: { basePath },
embeddable: embeddablePlugin,
maps: mapsPlugin,
},
@ -293,8 +287,7 @@ export const ExplorerChartsContainerUI = ({
severity={severity}
tooManyBuckets={tooManyBuckets}
wrapLabel={wrapLabel}
mlUrlGenerator={mlUrlGenerator}
basePath={basePath.get()}
mlLocator={mlLocator}
timeBuckets={timeBuckets}
timefilter={timefilter}
onSelectEntity={onSelectEntity}

View file

@ -22,7 +22,7 @@ import { ExplorerChartsData } from './explorer_charts/explorer_charts_container_
import { EXPLORER_ACTION } from './explorer_constants';
import { AppStateSelectedCells } from './explorer_utils';
import { explorerReducer, getExplorerDefaultState, ExplorerState } from './reducers';
import { ExplorerAppState } from '../../../common/types/ml_url_generator';
import { ExplorerAppState } from '../../../common/types/locator';
export const ALLOW_CELL_RANGE_SELECTION = true;

View file

@ -6,8 +6,8 @@
*/
import { usePageUrlState } from '../../util/url_state';
import { ExplorerAppState } from '../../../../common/types/ml_url_generator';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { ExplorerAppState } from '../../../../common/types/locator';
import { ML_PAGES } from '../../../../common/constants/locator';
export function useExplorerUrlState() {
/**

View file

@ -8,7 +8,7 @@
import moment from 'moment';
import { renderHook } from '@testing-library/react-hooks';
import { useSelectedCells } from './use_selected_cells';
import { ExplorerAppState } from '../../../../common/types/ml_url_generator';
import { ExplorerAppState } from '../../../../common/types/locator';
import { TimefilterContract } from '../../../../../../../src/plugins/data/public';
import { useTimefilter } from '../../contexts/kibana';

View file

@ -8,7 +8,7 @@
import { useCallback, useEffect, useMemo } from 'react';
import { SWIMLANE_TYPE } from '../explorer_constants';
import { AppStateSelectedCells } from '../explorer_utils';
import { ExplorerAppState } from '../../../../common/types/ml_url_generator';
import { ExplorerAppState } from '../../../../common/types/locator';
import { useTimefilter } from '../../contexts/kibana';
export const useSelectedCells = (

View file

@ -29,11 +29,7 @@ import {
isTimeSeriesViewJob,
} from '../../../../../../../common/util/job_utils';
import { withKibana } from '../../../../../../../../../../src/plugins/kibana_react/public';
import {
ML_APP_URL_GENERATOR,
ML_PAGES,
} from '../../../../../../../common/constants/ml_url_generator';
import { PLUGIN_ID } from '../../../../../../../common/constants/app';
import { ML_APP_LOCATOR, ML_PAGES } from '../../../../../../../common/constants/locator';
import { timeFormatter } from '../../../../../../../common/util/date_utils';
const MAX_FORECASTS = 500;
@ -86,11 +82,8 @@ export class ForecastsTableUI extends Component {
async openSingleMetricView(forecast) {
const {
services: {
application: { navigateToApp },
share: {
urlGenerators: { getUrlGenerator },
},
application: { navigateToUrl },
share,
},
} = this.props.kibana;
@ -126,39 +119,39 @@ export class ForecastsTableUI extends Component {
};
}
const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
const singleMetricViewerForecastLink = await mlUrlGenerator.createUrl({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
timeRange: {
from,
to,
mode: 'absolute',
},
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
jobIds: [this.props.job.job_id],
query: {
query_string: {
analyze_wildcard: true,
query: '*',
const mlLocator = share.url.locators.get(ML_APP_LOCATOR);
const singleMetricViewerForecastLink = await mlLocator.getUrl(
{
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
timeRange: {
from,
to,
mode: 'absolute',
},
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
jobIds: [this.props.job.job_id],
query: {
query_string: {
analyze_wildcard: true,
query: '*',
},
},
...mlTimeSeriesExplorer,
},
...mlTimeSeriesExplorer,
},
excludeBasePath: true,
});
{ absolute: true }
);
addItemToRecentlyAccessed(
'timeseriesexplorer',
this.props.job.job_id,
singleMetricViewerForecastLink
);
await navigateToApp(PLUGIN_ID, {
path: singleMetricViewerForecastLink,
});
await navigateToUrl(singleMetricViewerForecastLink);
}
render() {

View file

@ -7,9 +7,9 @@
import React, { useEffect, useState } from 'react';
import { EuiLink } from '@elastic/eui';
import { useMlUrlGenerator } from '../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { AnomalyDetectionQueryState } from '../../../../../../common/types/ml_url_generator';
import { useMlLocator } from '../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { AnomalyDetectionQueryState } from '../../../../../../common/types/locator';
// @ts-ignore
import { JobGroup } from '../job_group';
@ -28,7 +28,7 @@ function isGroupIdLink(props: JobIdLink | GroupIdLink): props is GroupIdLink {
return (props as GroupIdLink).groupId !== undefined;
}
export const AnomalyDetectionJobIdLink = (props: AnomalyDetectionJobIdLinkProps) => {
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
const [href, setHref] = useState<string>('');
useEffect(() => {
@ -40,19 +40,22 @@ export const AnomalyDetectionJobIdLink = (props: AnomalyDetectionJobIdLinkProps)
} else {
pageState.jobId = props.id;
}
const url = await mlUrlGenerator.createUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState,
});
if (!isCancelled) {
setHref(url);
if (mlLocator) {
const url = await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
// TODO: Fix this any.
pageState: pageState as any,
});
if (!isCancelled) {
setHref(url);
}
}
};
generateLink();
return () => {
isCancelled = true;
};
}, [props, mlUrlGenerator]);
}, [props, mlLocator]);
if (isGroupIdLink(props)) {
return (

View file

@ -13,7 +13,7 @@ import React from 'react';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../common/constants/locator';
export function NewJobButton() {
const buttonEnabled = checkPermission('canCreateJob') && mlNodesAvailable();

View file

@ -10,7 +10,7 @@ import { NavigationMenu } from '../../components/navigation_menu';
// @ts-ignore
import { JobsListView } from './components/jobs_list_view/index';
import { usePageUrlState } from '../../util/url_state';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../common/constants/locator';
import { ListingPageUrlState } from '../../../../common/types/common';
import { HelpMenu } from '../../components/help_menu';
import { useMlKibana } from '../../contexts/kibana';

View file

@ -27,7 +27,7 @@ import { PLUGIN_ID } from '../../../../../../../../../../../common/constants/app
import { Calendar } from '../../../../../../../../../../../common/types/calendars';
import { useMlKibana } from '../../../../../../../../../contexts/kibana';
import { GLOBAL_CALENDAR } from '../../../../../../../../../../../common/constants/calendars';
import { ML_PAGES } from '../../../../../../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../../../../../../common/constants/locator';
export const CalendarsSelection: FC = () => {
const {

View file

@ -19,7 +19,7 @@ import {
EuiLink,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useNavigateToPath } from '../../../../contexts/kibana';
import { useMlKibana, useNavigateToPath } from '../../../../contexts/kibana';
import { useMlContext } from '../../../../contexts/ml';
import { isSavedSearchSavedObject } from '../../../../../../common/types/kibana';
@ -28,11 +28,15 @@ import { addItemToRecentlyAccessed } from '../../../../util/recently_accessed';
import { timeBasedIndexCheck } from '../../../../util/index_utils';
import { LinkCard } from '../../../../components/link_card';
import { CategorizationIcon } from './categorization_job_icon';
import { ML_APP_LOCATOR, ML_PAGES } from '../../../../../../common/constants/locator';
import { RareIcon } from './rare_job_icon';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
export const Page: FC = () => {
const {
services: { share },
} = useMlKibana();
const mlContext = useMlContext();
const navigateToPath = useNavigateToPath();
const onSelectDifferentIndex = useCreateAndNavigateToMlLink(
@ -85,11 +89,25 @@ export const Page: FC = () => {
: `?savedSearchId=${currentSavedSearch.id}`;
};
const addSelectionToRecentlyAccessed = () => {
const addSelectionToRecentlyAccessed = async () => {
const title = !isSavedSearchSavedObject(currentSavedSearch)
? currentIndexPattern.title
: (currentSavedSearch.attributes.title as string);
addItemToRecentlyAccessed('jobs/new_job/datavisualizer', title, '');
const mlLocator = share.url.locators.get(ML_APP_LOCATOR)!;
const dataVisualizerLink = await mlLocator.getUrl(
{
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
pageState: {
...(currentSavedSearch?.id
? { savedSearchId: currentSavedSearch.id }
: { index: currentIndexPattern.id }),
},
},
{ absolute: true }
);
addItemToRecentlyAccessed(ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, title, dataVisualizerLink);
navigateToPath(`/jobs/new_job/datavisualizer${getUrlParams()}`);
};

View file

@ -23,7 +23,7 @@ import {
} from '@elastic/eui';
import { merge } from 'lodash';
import moment from 'moment';
import { useMlKibana, useMlUrlGenerator } from '../../../contexts/kibana';
import { useMlKibana, useMlLocator } from '../../../contexts/kibana';
import { ml } from '../../../services/ml_api_service';
import { useMlContext } from '../../../contexts/ml';
import {
@ -41,7 +41,7 @@ import { checkForSavedObjects } from './resolvers';
import { JobSettingsForm, JobSettingsFormValues } from './components/job_settings_form';
import { TimeRange } from '../common/components';
import { JobId } from '../../../../../common/types/anomaly_detection_jobs';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning';
import { isPopulatedObject } from '../../../../../common/util/object_utils';
@ -77,7 +77,7 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
const {
services: { notifications },
} = useMlKibana();
const urlGenerator = useMlUrlGenerator();
const locator = useMlLocator();
// #region State
const [jobPrefix, setJobPrefix] = useState<string>('');
@ -197,19 +197,21 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
);
setKibanaObjects(merge(kibanaObjects, kibanaResponse));
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: jobsResponse.filter(({ success }) => success).map(({ id }) => id),
timeRange: {
from: moment(resultTimeRange.start).format(TIME_FORMAT),
to: moment(resultTimeRange.end).format(TIME_FORMAT),
mode: 'absolute',
if (locator) {
const url = await locator.getUrl({
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: jobsResponse.filter(({ success }) => success).map(({ id }) => id),
timeRange: {
from: moment(resultTimeRange.start).format(TIME_FORMAT),
to: moment(resultTimeRange.end).format(TIME_FORMAT),
mode: 'absolute',
},
},
},
});
});
setResultsUrl(url);
}
setResultsUrl(url);
const failedJobsCount = jobsResponse.reduce(
(count, { success }) => (success ? count : count + 1),
0

View file

@ -12,7 +12,7 @@ import { useMlLink } from '../../../contexts/kibana';
import { getAnalysisType } from '../../../data_frame_analytics/common/analytics';
import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { getViewLinkStatus } from '../../../data_frame_analytics/pages/analytics_management/components/action_view/get_view_link_status';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
interface Props {

View file

@ -24,8 +24,8 @@ import { AnalyticsTable } from './table';
import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service';
import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { AnalyticStatsBarStats, StatsBar } from '../../../components/stats_bar';
import { useMlUrlGenerator, useNavigateToPath } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { SourceSelection } from '../../../data_frame_analytics/pages/analytics_management/components/source_selection';
interface Props {
@ -41,11 +41,12 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled, setLazyJobCount
const [isInitialized, setIsInitialized] = useState(false);
const [isSourceIndexModalVisible, setIsSourceIndexModalVisible] = useState(false);
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
const navigateToPath = useNavigateToPath();
const redirectToDataFrameAnalyticsManagementPage = async () => {
const path = await mlUrlGenerator.createUrl({
if (!mlLocator) return;
const path = await mlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
});
await navigateToPath(path, true);

View file

@ -17,13 +17,13 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { useMlKibana, useMlUrlGenerator, useNavigateToPath } from '../../../contexts/kibana';
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
import { AnomalyDetectionTable } from './table';
import { ml } from '../../../services/ml_api_service';
import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils';
import { Dictionary } from '../../../../../common/types/common';
import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/anomaly_detection_jobs';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
export type GroupsDictionary = Dictionary<Group>;
@ -59,18 +59,20 @@ export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled, setLazyJ
const {
services: { notifications },
} = useMlKibana();
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
const navigateToPath = useNavigateToPath();
const redirectToJobsManagementPage = async () => {
const path = await mlUrlGenerator.createUrl({
if (!mlLocator) return;
const path = await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
});
await navigateToPath(path, true);
};
const redirectToCreateJobSelectIndexPage = async () => {
const path = await mlUrlGenerator.createUrl({
if (!mlLocator) return;
const path = await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX,
});
await navigateToPath(path, true);

View file

@ -9,14 +9,14 @@ import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { NavigateToPath, useMlKibana, useMlUrlGenerator } from '../../../contexts/kibana';
import { NavigateToPath, useMlKibana, useMlLocator } from '../../../contexts/kibana';
import { MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_exploration';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
import { useUrlState } from '../../../util/url_state';
@ -43,7 +43,7 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const [globalState] = useUrlState('_g');
const urlGenerator = useMlUrlGenerator();
const locator = useMlLocator();
const {
services: {
application: { navigateToUrl },
@ -51,7 +51,8 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
} = useMlKibana();
const redirectToAnalyticsManagementPage = async () => {
const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
if (!locator) return;
const url = await locator.getUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
await navigateToUrl(url);
};

View file

@ -21,7 +21,7 @@ import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_ca
import { loadIndexPatterns } from '../../../util/index_utils';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
export const indexBasedRouteFactory = (

View file

@ -20,7 +20,7 @@ import { checkBasicLicense } from '../../../license';
import { loadIndexPatterns } from '../../../util/index_utils';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
enum MODE {

View file

@ -24,7 +24,7 @@ import {
import { checkCreateJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
interface WizardPageProps extends PageProps {
jobType: JOB_TYPE;

View file

@ -23,7 +23,7 @@ import { checkMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { NewCalendar } from '../../../settings/calendars';
import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
enum MODE {
NEW,

View file

@ -24,7 +24,7 @@ import { EditFilterList } from '../../../settings/filter_lists';
import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
enum MODE {
NEW,

View file

@ -43,7 +43,7 @@ import { useToastNotificationService } from '../../services/toast_notification_s
import { AnnotationUpdatesService } from '../../services/annotations_service';
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
import { useTimeSeriesExplorerUrlState } from '../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state';
import type { TimeSeriesExplorerAppState } from '../../../../common/types/ml_url_generator';
import type { TimeSeriesExplorerAppState } from '../../../../common/types/locator';
import type { TimeRangeBounds } from '../../util/time_buckets';
export const timeSeriesExplorerRouteFactory = (

View file

@ -19,7 +19,7 @@ import { ResolverResults, Resolvers } from './resolvers';
import { MlContextValue } from '../contexts/ml';
import { useNotifications } from '../contexts/kibana';
import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../common/constants/locator';
/**
* Hook to resolve route specific requirements

View file

@ -26,7 +26,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { AnomalyDetectionSettingsContext } from './anomaly_detection_settings_context';
import { useNotifications } from '../contexts/kibana';
import { ml } from '../services/ml_api_service';
import { ML_PAGES } from '../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../common/constants/locator';
import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url';
export const AnomalyDetectionSettings: FC = () => {

View file

@ -26,7 +26,7 @@ import { EventsTable } from '../events_table';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
function EditHeader({ calendarId, description }) {

View file

@ -21,7 +21,7 @@ import { ImportModal } from './import_modal';
import { ml } from '../../../services/ml_api_service';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { GLOBAL_CALENDAR } from '../../../../../common/constants/calendars';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { getDocLinks } from '../../../util/dependency_cache';
import { HelpMenu } from '../../../components/help_menu';

View file

@ -15,7 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { GLOBAL_CALENDAR } from '../../../../../../common/constants/calendars';
import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../../common/constants/locator';
export const CalendarsListTable = ({
calendarsList,

View file

@ -35,7 +35,7 @@ import { ItemsGrid } from '../../../components/items_grid';
import { NavigationMenu } from '../../../components/navigation_menu';
import { isValidFilterListId, saveFilterList } from './utils';
import { ml } from '../../../services/ml_api_service';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { getDocLinks } from '../../../util/dependency_cache';
import { HelpMenu } from '../../../components/help_menu';

View file

@ -28,7 +28,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { DeleteFilterListModal } from '../components/delete_filter_list_modal';
import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../../../common/constants/locator';
function UsedByIcon({ usedBy }) {
// Renders a tick or cross in the 'usedBy' column to indicate whether

View file

@ -13,15 +13,16 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { useMlUrlGenerator, useNavigateToPath } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/locator';
export const TimeseriesexplorerNoJobsFound = () => {
const mlUrlGenerator = useMlUrlGenerator();
const mlLocator = useMlLocator();
const navigateToPath = useNavigateToPath();
const redirectToJobsManagementPage = async () => {
const path = await mlUrlGenerator.createUrl({
if (!mlLocator) return;
const path = await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
});
await navigateToPath(path, true);

View file

@ -6,8 +6,8 @@
*/
import { usePageUrlState } from '../../util/url_state';
import { TimeSeriesExplorerAppState } from '../../../../common/types/ml_url_generator';
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
import { TimeSeriesExplorerAppState } from '../../../../common/types/locator';
import { ML_PAGES } from '../../../../common/constants/locator';
export function useTimeSeriesExplorerUrlState() {
return usePageUrlState<TimeSeriesExplorerAppState>(ML_PAGES.SINGLE_METRIC_VIEWER);

View file

@ -10,7 +10,7 @@ import { calculateTextWidth } from './string_utils';
import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact';
import moment from 'moment';
import { CHART_TYPE } from '../explorer/explorer_constants';
import { ML_PAGES } from '../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../common/constants/locator';
export const LINE_CHART_ANOMALY_RADIUS = 7;
export const MULTI_BUCKET_SYMBOL_SIZE = 100; // In square pixels for use with d3 symbol.size
@ -219,7 +219,7 @@ export function getChartType(config) {
return chartType;
}
export async function getExploreSeriesLink(mlUrlGenerator, series, timefilter) {
export async function getExploreSeriesLink(mlLocator, series, timefilter) {
// Open the Single Metric dashboard over the same overall bounds and
// zoomed in to the same time as the current chart.
const bounds = timefilter.getActiveBounds();
@ -241,35 +241,37 @@ export async function getExploreSeriesLink(mlUrlGenerator, series, timefilter) {
});
}
const url = await mlUrlGenerator.createUrl({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: [series.jobId],
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
timeRange: {
from: from,
to: to,
mode: 'absolute',
},
zoom: {
from: zoomFrom,
to: zoomTo,
},
detectorIndex: series.detectorIndex,
entities: entityCondition,
query: {
query_string: {
analyze_wildcard: true,
query: '*',
const url = await mlLocator.getUrl(
{
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: [series.jobId],
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
timeRange: {
from: from,
to: to,
mode: 'absolute',
},
zoom: {
from: zoomFrom,
to: zoomTo,
},
detectorIndex: series.detectorIndex,
entities: entityCondition,
query: {
query_string: {
analyze_wildcard: true,
query: '*',
},
},
},
},
excludeBasePath: true,
});
{ absolute: true }
);
return url;
}

View file

@ -44,7 +44,6 @@ export function addItemToRecentlyAccessed(
return;
}
url = url.startsWith('/') ? `/app/ml${url}` : `/app/ml/${page}/${url}`;
const recentlyAccessed = recentlyAccessedService ?? getRecentlyAccessed();
recentlyAccessed.add(url, `ML - ${itemId} - ${pageLabel}`, id);
}

View file

@ -14,7 +14,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';
import { MlPages } from '../../../common/constants/locator';
type Accessor = '_a' | '_g';
export type SetUrlState = (

View file

@ -10,7 +10,7 @@ Object {
"tooManyBuckets": false,
},
"id": "test-explorer-charts-embeddable",
"mlUrlGenerator": undefined,
"mlLocator": undefined,
"onSelectEntity": [Function],
"setSeverity": [Function],
"severity": Object {

View file

@ -20,12 +20,13 @@ import type {
import type { EntityField, EntityFieldOperation } from '../../../common/util/anomaly_utils';
import { ExplorerAnomaliesContainer } from '../../application/explorer/explorer_charts/explorer_anomalies_container';
import { ML_APP_URL_GENERATOR } from '../../../common/constants/ml_url_generator';
import { ML_APP_LOCATOR } from '../../../common/constants/locator';
import { optionValueToThreshold } from '../../application/components/controls/select_severity/select_severity';
import { ANOMALY_THRESHOLD } from '../../../common';
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
import { TimeBuckets } from '../../application/util/time_buckets';
import { EXPLORER_ENTITY_FIELD_SELECTION_TRIGGER } from '../../ui_actions/triggers';
import { MlLocatorParams } from '../../../common/types/locator';
const RESIZE_THROTTLE_TIME_MS = 500;
@ -55,19 +56,12 @@ export const EmbeddableAnomalyChartsContainer: FC<EmbeddableAnomalyChartsContain
)
);
const [selectedEntities, setSelectedEntities] = useState<EntityField[] | undefined>();
const [
{ uiSettings },
{
data: dataServices,
share: {
urlGenerators: { getUrlGenerator },
},
uiActions,
},
] = services;
const [{ uiSettings }, { data: dataServices, share, uiActions }] = services;
const { timefilter } = dataServices.query.timefilter;
const mlUrlGenerator = useMemo(() => getUrlGenerator(ML_APP_URL_GENERATOR), [getUrlGenerator]);
const mlLocator = useMemo(() => share.url.locators.get<MlLocatorParams>(ML_APP_LOCATOR)!, [
share,
]);
const timeBuckets = useMemo(() => {
return new TimeBuckets({
@ -179,7 +173,7 @@ export const EmbeddableAnomalyChartsContainer: FC<EmbeddableAnomalyChartsContain
chartsData={chartsData}
severity={severity}
setSeverity={setSeverity}
mlUrlGenerator={mlUrlGenerator}
mlLocator={mlLocator}
timeBuckets={timeBuckets}
timefilter={timefilter}
onSelectEntity={addEntityFieldFilter}

View file

@ -54,7 +54,7 @@ export {
export { ES_CLIENT_TOTAL_HITS_RELATION } from '../common/types/es_client';
export { ANOMALY_SEVERITY } from '../common';
export { useMlHref, ML_PAGES, MlUrlGenerator } from './ml_url_generator';
export { useMlHref, ML_PAGES, MlLocator, MlLocatorDefinition } from './locator';
// Bundled shared exports
// Exported this way so the code doesn't end up in ML's page load bundle

View file

@ -16,16 +16,17 @@ import type {
TimeSeriesExplorerAppState,
TimeSeriesExplorerGlobalState,
TimeSeriesExplorerUrlState,
} from '../../common/types/ml_url_generator';
import { ML_PAGES } from '../../common/constants/ml_url_generator';
import { createGenericMlUrl } from './common';
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
import { getGroupQueryText, getJobQueryText } from '../../common/util/string_utils';
import { AppPageState, ListingPageUrlState } from '../../common/types/common';
} from '../../../common/types/locator';
import { ML_PAGES } from '../../../common/constants/locator';
import { formatGenericMlUrl } from './common';
import { setStateToKbnUrl } from '../../../../../../src/plugins/kibana_utils/public';
import { getGroupQueryText, getJobQueryText } from '../../../common/util/string_utils';
import { AppPageState, ListingPageUrlState } from '../../../common/types/common';
/**
* Creates URL to the Anomaly Detection Job management page
*/
export function createAnomalyDetectionJobManagementUrl(
export function formatAnomalyDetectionJobManagementUrl(
appBasePath: string,
params: AnomalyDetectionUrlState['pageState']
): string {
@ -69,22 +70,22 @@ export function createAnomalyDetectionJobManagementUrl(
return url;
}
export function createAnomalyDetectionCreateJobSelectType(
export function formatAnomalyDetectionCreateJobSelectType(
appBasePath: string,
pageState: MlGenericUrlState['pageState']
): string {
return createGenericMlUrl(
return formatGenericMlUrl(
appBasePath,
ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
pageState
);
}
export function createAnomalyDetectionCreateJobSelectIndex(
export function formatAnomalyDetectionCreateJobSelectIndex(
appBasePath: string,
pageState: MlGenericUrlState['pageState']
): string {
return createGenericMlUrl(
return formatGenericMlUrl(
appBasePath,
ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX,
pageState
@ -94,7 +95,7 @@ export function createAnomalyDetectionCreateJobSelectIndex(
/**
* Creates URL to the Anomaly Explorer page
*/
export function createExplorerUrl(
export function formatExplorerUrl(
appBasePath: string,
params: ExplorerUrlState['pageState']
): string {
@ -146,7 +147,7 @@ export function createExplorerUrl(
/**
* Creates URL to the SingleMetricViewer page
*/
export function createSingleMetricViewerUrl(
export function formatSingleMetricViewerUrl(
appBasePath: string,
params: TimeSeriesExplorerUrlState['pageState']
): string {

View file

@ -6,8 +6,8 @@
*/
import { isEmpty } from 'lodash';
import { MlGenericUrlState } from '../../common/types/ml_url_generator';
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
import { MlGenericUrlState } from '../../../common/types/locator';
import { setStateToKbnUrl } from '../../../../../../src/plugins/kibana_utils/public';
export function extractParams<UrlState>(urlState: UrlState) {
// page should be guaranteed to exist here but <UrlState> is unknown
@ -20,7 +20,7 @@ export function extractParams<UrlState>(urlState: UrlState) {
* Creates generic index based search ML url
* e.g. `jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a`
*/
export function createGenericMlUrl(
export function formatGenericMlUrl(
appBasePath: string,
page: MlGenericUrlState['page'],
pageState: MlGenericUrlState['pageState']

View file

@ -9,6 +9,7 @@
* Creates URL to the DataFrameAnalytics page
*/
import { isEmpty } from 'lodash';
import { formatGenericMlUrl } from './common';
import {
DataFrameAnalyticsExplorationQueryState,
DataFrameAnalyticsExplorationUrlState,
@ -16,14 +17,13 @@ import {
ExplorationPageUrlState,
MlGenericUrlState,
MlCommonGlobalState,
} from '../../common/types/ml_url_generator';
import { createGenericMlUrl } from './common';
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';
} from '../../../common/types/locator';
import { ML_PAGES } from '../../../common/constants/locator';
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(
export function formatDataFrameAnalyticsJobManagementUrl(
appBasePath: string,
mlUrlGeneratorState: DataFrameAnalyticsUrlState['pageState']
): string {
@ -70,7 +70,7 @@ export function createDataFrameAnalyticsJobManagementUrl(
/**
* Creates URL to the DataFrameAnalytics Exploration page
*/
export function createDataFrameAnalyticsExplorationUrl(
export function formatDataFrameAnalyticsExplorationUrl(
appBasePath: string,
mlUrlGeneratorState: DataFrameAnalyticsExplorationUrlState['pageState']
): string {
@ -116,17 +116,17 @@ export function createDataFrameAnalyticsExplorationUrl(
/**
* Creates URL to the DataFrameAnalytics creation wizard
*/
export function createDataFrameAnalyticsCreateJobUrl(
export function formatDataFrameAnalyticsCreateJobUrl(
appBasePath: string,
pageState: MlGenericUrlState['pageState']
): string {
return createGenericMlUrl(appBasePath, ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB, pageState);
return formatGenericMlUrl(appBasePath, ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB, pageState);
}
/**
* Creates URL to the DataFrameAnalytics Map page
*/
export function createDataFrameAnalyticsMapUrl(
export function formatDataFrameAnalyticsMapUrl(
appBasePath: string,
mlUrlGeneratorState: DataFrameAnalyticsExplorationUrlState['pageState']
): string {

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './common';
export * from './anomaly_detection';
export * from './data_frame_analytics';
export * from './settings';

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import { CalendarEditUrlState, FilterEditUrlState } from '../../common/types/ml_url_generator';
import { ML_PAGES } from '../../common/constants/ml_url_generator';
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
import { CalendarEditUrlState, FilterEditUrlState } from '../../../common/types/locator';
import { ML_PAGES } from '../../../common/constants/locator';
import { setStateToKbnUrl } from '../../../../../../src/plugins/kibana_utils/public';
export function createEditCalendarUrl(
export function formatEditCalendarUrl(
appBasePath: string,
pageState: CalendarEditUrlState['pageState']
): string {
@ -27,7 +27,7 @@ export function createEditCalendarUrl(
return url;
}
export function createEditFilterUrl(
export function formatEditFilterUrl(
appBasePath: string,
pageState: FilterEditUrlState['pageState']
): string {

View file

@ -5,6 +5,6 @@
* 2.0.
*/
export { MlUrlGenerator, registerUrlGenerator } from './ml_url_generator';
export { ML_PAGES } from '../../common/constants/locator';
export * from './ml_locator';
export { useMlHref } from './use_ml_href';
export { ML_PAGES } from '../../common/constants/ml_url_generator';

View file

@ -0,0 +1,336 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { MlLocatorDefinition } from './ml_locator';
import { ML_PAGES } from '../../common/constants/locator';
import { ANALYSIS_CONFIG_TYPE } from '../../common/constants/data_frame_analytics';
describe('MlUrlGenerator', () => {
const definition = new MlLocatorDefinition();
describe('AnomalyDetection', () => {
it('should throw an error in case the page is not provided', async () => {
expect.assertions(1);
await definition.getLocation({ jobIds: ['test-job'] } as any).catch((e) => {
expect(e.message).toEqual('Page type is not provided or unknown');
});
});
describe('Job Management Page', () => {
it('should generate valid URL for the Anomaly Detection job management page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
});
expect(location).toMatchObject({
app: 'ml',
path: '/jobs',
state: {},
});
});
it('should generate valid URL for the Anomaly Detection job management page for job', async () => {
const location = await definition.getLocation({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
jobId: 'fq_single_1',
},
});
expect(location).toMatchObject({
app: 'ml',
path: "/jobs?_a=(jobs:(queryText:'id:fq_single_1'))",
state: {},
});
});
it('should generate valid URL for the Anomaly Detection job management page for groupIds', async () => {
const location = await definition.getLocation({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
groupIds: ['farequote', 'categorization'],
},
});
expect(location).toMatchObject({
app: 'ml',
path: "/jobs?_a=(jobs:(queryText:'groups:(farequote%20or%20categorization)'))",
state: {},
});
});
it('should generate valid URL for the page for selecting the type of anomaly detection job to create', async () => {
const location = await definition.getLocation({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
pageState: {
index: `3da93760-e0af-11ea-9ad3-3bcfc330e42a`,
globalState: {
time: {
from: 'now-30m',
to: 'now',
},
},
},
});
expect(location).toMatchObject({
app: 'ml',
path:
'/jobs/new_job/step/job_type?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))',
state: {},
});
});
});
describe('Anomaly Explorer Page', () => {
it('should generate valid URL for the Anomaly Explorer page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: ['fq_single_1'],
mlExplorerSwimlane: { viewByFromPage: 2, viewByPerPage: 20 },
refreshInterval: {
pause: false,
value: 0,
},
timeRange: {
from: '2019-02-07T00:00:00.000Z',
to: '2020-08-13T17:15:00.000Z',
mode: 'absolute',
},
query: {
analyze_wildcard: true,
query: '*',
},
},
});
expect(location).toMatchObject({
app: 'ml',
path:
"/explorer?_g=(ml:(jobIds:!(fq_single_1)),refreshInterval:(pause:!f,value:0),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20),query:(analyze_wildcard:!t,query:'*')))",
state: {},
});
});
it('should generate valid URL for the Anomaly Explorer page for multiple jobIds', async () => {
const location = await definition.getLocation({
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: ['fq_single_1', 'logs_categorization_1'],
timeRange: {
from: '2019-02-07T00:00:00.000Z',
to: '2020-08-13T17:15:00.000Z',
mode: 'absolute',
},
},
});
expect(location).toMatchObject({
app: 'ml',
path:
"/explorer?_g=(ml:(jobIds:!(fq_single_1,logs_categorization_1)),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))",
state: {},
});
});
});
describe('Single Metric Viewer Page', () => {
it('should generate valid URL for the Single Metric Viewer page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: ['logs_categorization_1'],
refreshInterval: {
pause: false,
value: 0,
},
timeRange: {
from: '2020-07-12T00:39:02.912Z',
to: '2020-07-22T15:52:18.613Z',
mode: 'absolute',
},
query: {
analyze_wildcard: true,
query: '*',
},
},
});
expect(location).toMatchObject({
app: 'ml',
path:
"/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*'))))",
state: {},
});
});
it('should generate valid URL for the Single Metric Viewer page with extra settings', async () => {
const location = await definition.getLocation({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: ['logs_categorization_1'],
detectorIndex: 0,
entities: { mlcategory: '2' },
refreshInterval: {
pause: false,
value: 0,
},
timeRange: {
from: '2020-07-12T00:39:02.912Z',
to: '2020-07-22T15:52:18.613Z',
mode: 'absolute',
},
zoom: {
from: '2020-07-20T23:58:29.367Z',
to: '2020-07-21T11:00:13.173Z',
},
query: {
analyze_wildcard: true,
query: '*',
},
},
});
expect(location).toMatchObject({
app: 'ml',
path:
"/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2'),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z')),query:(query_string:(analyze_wildcard:!t,query:'*'))))",
state: {},
});
});
});
describe('DataFrameAnalytics', () => {
describe('JobManagement Page', () => {
it('should generate valid URL for the Data Frame Analytics job management page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
});
expect(location).toMatchObject({
app: 'ml',
path: '/data_frame_analytics',
state: {},
});
});
it('should generate valid URL for the Data Frame Analytics job management page with jobId', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
jobId: 'grid_regression_1',
},
});
expect(location).toMatchObject({
app: 'ml',
path:
"/data_frame_analytics?_a=(data_frame_analytics:(queryText:'id:grid_regression_1'))",
state: {},
});
});
it('should generate valid URL for the Data Frame Analytics job management page with groupIds', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
groupIds: ['group_1', 'group_2'],
},
});
expect(location).toMatchObject({
app: 'ml',
path:
"/data_frame_analytics?_a=(data_frame_analytics:(queryText:'groups:(group_1%20or%20group_2)'))",
state: {},
});
});
});
describe('ExplorationPage', () => {
it('should generate valid URL for the Data Frame Analytics exploration page for job', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId: 'grid_regression_1',
analysisType: ANALYSIS_CONFIG_TYPE.REGRESSION,
},
});
expect(location).toMatchObject({
app: 'ml',
path:
'/data_frame_analytics/exploration?_g=(ml:(analysisType:regression,jobId:grid_regression_1))',
state: {},
});
});
});
});
describe('DataVisualizer', () => {
it('should generate valid URL for the Data Visualizer page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_VISUALIZER,
});
expect(location).toMatchObject({
app: 'ml',
path: '/datavisualizer',
state: {},
});
});
it('should generate valid URL for the File Data Visualizer import page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_VISUALIZER_FILE,
});
expect(location).toMatchObject({
app: 'ml',
path: '/filedatavisualizer',
state: {},
});
});
it('should generate valid URL for the Index Data Visualizer select index pattern or saved search page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT,
});
expect(location).toMatchObject({
app: 'ml',
path: '/datavisualizer_index_select',
state: {},
});
});
it('should generate valid URL for the Index Data Visualizer Viewer page', async () => {
const location = await definition.getLocation({
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
pageState: {
index: '3da93760-e0af-11ea-9ad3-3bcfc330e42a',
globalState: {
time: {
from: 'now-30m',
to: 'now',
},
},
},
});
expect(location).toMatchObject({
app: 'ml',
path:
'/jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))',
state: {},
});
});
});
});
});

View file

@ -0,0 +1,101 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { LocatorDefinition, KibanaLocation } from '../../../../../src/plugins/share/public';
import {
DataFrameAnalyticsExplorationUrlState,
MlLocatorParams,
MlLocator,
} from '../../common/types/locator';
import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator';
import {
formatAnomalyDetectionCreateJobSelectIndex,
formatAnomalyDetectionCreateJobSelectType,
formatAnomalyDetectionJobManagementUrl,
formatExplorerUrl,
formatSingleMetricViewerUrl,
formatDataFrameAnalyticsCreateJobUrl,
formatDataFrameAnalyticsExplorationUrl,
formatDataFrameAnalyticsJobManagementUrl,
formatDataFrameAnalyticsMapUrl,
formatGenericMlUrl,
formatEditCalendarUrl,
formatEditFilterUrl,
} from './formatters';
export { MlLocatorParams, MlLocator };
export class MlLocatorDefinition implements LocatorDefinition<MlLocatorParams> {
public readonly id = ML_APP_LOCATOR;
public readonly getLocation = async (params: MlLocatorParams): Promise<KibanaLocation> => {
let path: string = '';
switch (params.page) {
case ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE:
path = formatAnomalyDetectionJobManagementUrl('', params.pageState);
break;
case ML_PAGES.ANOMALY_EXPLORER:
path = formatExplorerUrl('', params.pageState);
break;
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE:
path = formatAnomalyDetectionCreateJobSelectType('', params.pageState);
break;
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX:
path = formatAnomalyDetectionCreateJobSelectIndex('', params.pageState);
break;
case ML_PAGES.SINGLE_METRIC_VIEWER:
path = formatSingleMetricViewerUrl('', params.pageState);
break;
case ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE:
path = formatDataFrameAnalyticsJobManagementUrl('', params.pageState);
break;
case ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB:
path = formatDataFrameAnalyticsCreateJobUrl('', params.pageState);
break;
case ML_PAGES.DATA_FRAME_ANALYTICS_MAP:
path = formatDataFrameAnalyticsMapUrl(
'',
params.pageState as DataFrameAnalyticsExplorationUrlState['pageState']
);
break;
case ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION:
path = formatDataFrameAnalyticsExplorationUrl('', params.pageState);
break;
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB:
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED:
case ML_PAGES.DATA_VISUALIZER:
case ML_PAGES.DATA_VISUALIZER_FILE:
case ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER:
case ML_PAGES.DATA_VISUALIZER_INDEX_SELECT:
case ML_PAGES.OVERVIEW:
case ML_PAGES.SETTINGS:
case ML_PAGES.FILTER_LISTS_MANAGE:
case ML_PAGES.FILTER_LISTS_NEW:
case ML_PAGES.CALENDARS_MANAGE:
case ML_PAGES.CALENDARS_NEW:
case ML_PAGES.ACCESS_DENIED:
path = formatGenericMlUrl('', params.page, params.pageState);
break;
case ML_PAGES.FILTER_LISTS_EDIT:
path = formatEditFilterUrl('', params.pageState);
break;
case ML_PAGES.CALENDARS_EDIT:
path = formatEditCalendarUrl('', params.pageState);
break;
default:
throw new Error('Page type is not provided or unknown');
}
return {
app: 'ml',
path,
state: {},
};
};
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { MlPluginStart } from '../index';
import { MlLocatorParams } from '../../common/types/locator';
/**
* Provides a URL to ML plugin page
* TODO remove basePath parameter
*/
export const useMlHref = (
ml: MlPluginStart | undefined,
basePath: string | undefined,
params: MlLocatorParams
) => {
return ml && ml.locator
? ml.locator!.useUrl(params)
: basePath !== undefined
? `${basePath}/app/ml/${params.page}`
: '';
};

View file

@ -1,17 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ML_APP_URL_GENERATOR } from '../../../common/constants/ml_url_generator';
import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public';
export const createMlUrlGeneratorMock = () =>
({
id: ML_APP_URL_GENERATOR,
isDeprecated: false,
createUrl: jest.fn(),
migrate: jest.fn(),
} as jest.Mocked<UrlGeneratorContract<typeof ML_APP_URL_GENERATOR>>);

View file

@ -1,269 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { MlUrlGenerator } from './ml_url_generator';
import { ML_PAGES } from '../../common/constants/ml_url_generator';
import { ANALYSIS_CONFIG_TYPE } from '../../common/constants/data_frame_analytics';
describe('MlUrlGenerator', () => {
const urlGenerator = new MlUrlGenerator({
appBasePath: '/app/ml',
useHash: false,
});
describe('AnomalyDetection', () => {
describe('Job Management Page', () => {
it('should generate valid URL for the Anomaly Detection job management page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
});
expect(url).toBe('/app/ml/jobs');
});
it('should generate valid URL for the Anomaly Detection job management page for job', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
jobId: '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 () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
groupIds: ['farequote', 'categorization'],
},
});
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 () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
pageState: {
index: `3da93760-e0af-11ea-9ad3-3bcfc330e42a`,
globalState: {
time: {
from: 'now-30m',
to: 'now',
},
},
},
});
expect(url).toBe(
'/app/ml/jobs/new_job/step/job_type?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))'
);
});
});
describe('Anomaly Explorer Page', () => {
it('should generate valid URL for the Anomaly Explorer page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: ['fq_single_1'],
mlExplorerSwimlane: { viewByFromPage: 2, viewByPerPage: 20 },
refreshInterval: {
pause: false,
value: 0,
},
timeRange: {
from: '2019-02-07T00:00:00.000Z',
to: '2020-08-13T17:15:00.000Z',
mode: 'absolute',
},
query: {
analyze_wildcard: true,
query: '*',
},
},
});
expect(url).toBe(
"/app/ml/explorer?_g=(ml:(jobIds:!(fq_single_1)),refreshInterval:(pause:!f,value:0),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20),query:(analyze_wildcard:!t,query:'*')))"
);
});
it('should generate valid URL for the Anomaly Explorer page for multiple jobIds', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: ['fq_single_1', 'logs_categorization_1'],
timeRange: {
from: '2019-02-07T00:00:00.000Z',
to: '2020-08-13T17:15:00.000Z',
mode: 'absolute',
},
},
});
expect(url).toBe(
"/app/ml/explorer?_g=(ml:(jobIds:!(fq_single_1,logs_categorization_1)),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"
);
});
});
describe('Single Metric Viewer Page', () => {
it('should generate valid URL for the Single Metric Viewer page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: ['logs_categorization_1'],
refreshInterval: {
pause: false,
value: 0,
},
timeRange: {
from: '2020-07-12T00:39:02.912Z',
to: '2020-07-22T15:52:18.613Z',
mode: 'absolute',
},
query: {
analyze_wildcard: true,
query: '*',
},
},
});
expect(url).toBe(
"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*'))))"
);
});
it('should generate valid URL for the Single Metric Viewer page with extra settings', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
jobIds: ['logs_categorization_1'],
detectorIndex: 0,
entities: { mlcategory: '2' },
refreshInterval: {
pause: false,
value: 0,
},
timeRange: {
from: '2020-07-12T00:39:02.912Z',
to: '2020-07-22T15:52:18.613Z',
mode: 'absolute',
},
zoom: {
from: '2020-07-20T23:58:29.367Z',
to: '2020-07-21T11:00:13.173Z',
},
query: {
analyze_wildcard: true,
query: '*',
},
},
});
expect(url).toBe(
"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2'),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z')),query:(query_string:(analyze_wildcard:!t,query:'*'))))"
);
});
});
});
describe('DataFrameAnalytics', () => {
describe('JobManagement Page', () => {
it('should generate valid URL for the Data Frame Analytics job management page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
});
expect(url).toBe('/app/ml/data_frame_analytics');
});
it('should generate valid URL for the Data Frame Analytics job management page with jobId', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
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 () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
groupIds: ['group_1', 'group_2'],
},
});
expect(url).toBe(
"/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:'groups:(group_1%20or%20group_2)'))"
);
});
});
describe('ExplorationPage', () => {
it('should generate valid URL for the Data Frame Analytics exploration page for job', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId: 'grid_regression_1',
analysisType: ANALYSIS_CONFIG_TYPE.REGRESSION,
},
});
expect(url).toBe(
'/app/ml/data_frame_analytics/exploration?_g=(ml:(analysisType:regression,jobId:grid_regression_1))'
);
});
});
});
describe('DataVisualizer', () => {
it('should generate valid URL for the Data Visualizer page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_VISUALIZER,
});
expect(url).toBe('/app/ml/datavisualizer');
});
it('should generate valid URL for the File Data Visualizer import page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_VISUALIZER_FILE,
});
expect(url).toBe('/app/ml/filedatavisualizer');
});
it('should generate valid URL for the Index Data Visualizer select index pattern or saved search page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT,
});
expect(url).toBe('/app/ml/datavisualizer_index_select');
});
it('should generate valid URL for the Index Data Visualizer Viewer page', async () => {
const url = await urlGenerator.createUrl({
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
pageState: {
index: '3da93760-e0af-11ea-9ad3-3bcfc330e42a',
globalState: {
time: {
from: 'now-30m',
to: 'now',
},
},
},
});
expect(url).toBe(
'/app/ml/jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))'
);
});
});
it('should throw an error in case the page is not provided', async () => {
expect.assertions(1);
// @ts-ignore
await urlGenerator.createUrl({ jobIds: ['test-job'] }).catch((e) => {
expect(e.message).toEqual('Page type is not provided or unknown');
});
});
});

View file

@ -1,124 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreSetup } from 'kibana/public';
import type {
SharePluginSetup,
UrlGeneratorsDefinition,
UrlGeneratorState,
} from '../../../../../src/plugins/share/public';
import type { MlStartDependencies } from '../plugin';
import { ML_PAGES, ML_APP_URL_GENERATOR } from '../../common/constants/ml_url_generator';
import type { MlUrlGeneratorState } from '../../common/types/ml_url_generator';
import {
createAnomalyDetectionJobManagementUrl,
createAnomalyDetectionCreateJobSelectType,
createAnomalyDetectionCreateJobSelectIndex,
createExplorerUrl,
createSingleMetricViewerUrl,
} from './anomaly_detection_urls_generator';
import {
createDataFrameAnalyticsJobManagementUrl,
createDataFrameAnalyticsCreateJobUrl,
createDataFrameAnalyticsExplorationUrl,
createDataFrameAnalyticsMapUrl,
} from './data_frame_analytics_urls_generator';
import { createGenericMlUrl } from './common';
import { createEditCalendarUrl, createEditFilterUrl } from './settings_urls_generator';
declare module '../../../../../src/plugins/share/public' {
export interface UrlGeneratorStateMapping {
[ML_APP_URL_GENERATOR]: UrlGeneratorState<MlUrlGeneratorState>;
}
}
interface Params {
appBasePath: string;
useHash: boolean;
}
export class MlUrlGenerator implements UrlGeneratorsDefinition<typeof ML_APP_URL_GENERATOR> {
constructor(private readonly params: Params) {}
public readonly id = ML_APP_URL_GENERATOR;
public readonly createUrl = async (
mlUrlGeneratorParams: MlUrlGeneratorState
): Promise<string> => {
const { excludeBasePath, ...mlUrlGeneratorState } = mlUrlGeneratorParams;
const appBasePath = excludeBasePath === true ? '' : this.params.appBasePath;
switch (mlUrlGeneratorState.page) {
case ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE:
return createAnomalyDetectionJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.ANOMALY_EXPLORER:
return createExplorerUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE:
return createAnomalyDetectionCreateJobSelectType(
appBasePath,
mlUrlGeneratorState.pageState
);
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX:
return createAnomalyDetectionCreateJobSelectIndex(
appBasePath,
mlUrlGeneratorState.pageState
);
case ML_PAGES.SINGLE_METRIC_VIEWER:
return createSingleMetricViewerUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE:
return createDataFrameAnalyticsJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB:
return createDataFrameAnalyticsCreateJobUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.DATA_FRAME_ANALYTICS_MAP:
// @ts-ignore // TODO: fix type
return createDataFrameAnalyticsMapUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION:
return createDataFrameAnalyticsExplorationUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB:
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED:
case ML_PAGES.DATA_VISUALIZER:
case ML_PAGES.DATA_VISUALIZER_FILE:
case ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER:
case ML_PAGES.DATA_VISUALIZER_INDEX_SELECT:
case ML_PAGES.OVERVIEW:
case ML_PAGES.SETTINGS:
case ML_PAGES.FILTER_LISTS_MANAGE:
case ML_PAGES.FILTER_LISTS_NEW:
case ML_PAGES.CALENDARS_MANAGE:
case ML_PAGES.CALENDARS_NEW:
case ML_PAGES.ACCESS_DENIED:
return createGenericMlUrl(
appBasePath,
mlUrlGeneratorState.page,
mlUrlGeneratorState.pageState
);
case ML_PAGES.FILTER_LISTS_EDIT:
return createEditFilterUrl(appBasePath, mlUrlGeneratorState.pageState);
case ML_PAGES.CALENDARS_EDIT:
return createEditCalendarUrl(appBasePath, mlUrlGeneratorState.pageState);
default:
throw new Error('Page type is not provided or unknown');
}
};
}
/**
* Registers the URL generator
*/
export function registerUrlGenerator(
share: SharePluginSetup,
core: CoreSetup<MlStartDependencies>
) {
const baseUrl = core.http.basePath.prepend('/app/ml');
return share.urlGenerators.registerUrlGenerator(
new MlUrlGenerator({
appBasePath: baseUrl,
useHash: core.uiSettings.get('state:storeInSessionStorage'),
})
);
}

View file

@ -1,42 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect, useState } from 'react';
import { MlPluginStart } from '../index';
import { MlUrlGeneratorState } from '../../common/types/ml_url_generator';
/**
* Provides a URL to ML plugin page
* TODO remove basePath parameter
*/
export const useMlHref = (
ml: MlPluginStart | undefined,
basePath: string | undefined,
params: MlUrlGeneratorState
) => {
const [mlLink, setMlLink] = useState<string | undefined>(
basePath !== undefined ? `${basePath}/app/ml/${params.page}` : undefined
);
useEffect(() => {
let isCancelled = false;
const generateLink = async () => {
if (ml?.urlGenerator !== undefined) {
const href = await ml.urlGenerator.createUrl(params);
if (!isCancelled) {
setMlLink(href);
}
}
};
generateLink();
return () => {
isCancelled = true;
};
}, [ml?.urlGenerator, params]);
return mlLink;
};

View file

@ -4,17 +4,35 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createMlUrlGeneratorMock } from './ml_url_generator/__mocks__/ml_url_generator';
import { MlPluginSetup, MlPluginStart } from './plugin';
const createSetupContract = (): jest.Mocked<MlPluginSetup> => {
return {
urlGenerator: createMlUrlGeneratorMock(),
locator: {
getLocation: jest.fn(),
getUrl: jest.fn(),
useUrl: jest.fn(),
navigate: jest.fn(),
extract: jest.fn(),
inject: jest.fn(),
telemetry: jest.fn(),
migrations: {},
},
};
};
const createStartContract = (): jest.Mocked<MlPluginStart> => {
return {
urlGenerator: createMlUrlGeneratorMock(),
locator: {
getLocation: jest.fn(),
getUrl: jest.fn(),
useUrl: jest.fn(),
navigate: jest.fn(),
extract: jest.fn(),
inject: jest.fn(),
telemetry: jest.fn(),
migrations: {},
},
};
};

View file

@ -17,11 +17,7 @@ import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import type { ManagementSetup } from 'src/plugins/management/public';
import type {
SharePluginSetup,
SharePluginStart,
UrlGeneratorContract,
} from 'src/plugins/share/public';
import type { SharePluginSetup, SharePluginStart } from 'src/plugins/share/public';
import type { DataPublicPluginStart } from 'src/plugins/data/public';
import type { HomePublicPluginSetup } from 'src/plugins/home/public';
import type { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
@ -36,13 +32,11 @@ import type { LicensingPluginSetup } from '../../licensing/public';
import type { SecurityPluginSetup } from '../../security/public';
import { PLUGIN_ICON_SOLUTION, PLUGIN_ID } from '../common/constants/app';
import { ML_APP_URL_GENERATOR } from '../common/constants/ml_url_generator';
import { isFullLicense, isMlEnabled } from '../common/license';
import { setDependencyCache } from './application/util/dependency_cache';
import { registerFeature } from './register_feature';
// Not importing from `ml_url_generator/index` here to avoid importing unnecessary code
import { registerUrlGenerator } from './ml_url_generator/ml_url_generator';
import { MlLocatorDefinition, MlLocator } from './locator';
import type { MapsStartApi } from '../../maps/public';
import {
TriggersAndActionsUIPublicPluginSetup,
@ -84,7 +78,8 @@ export type MlCoreSetup = CoreSetup<MlStartDependencies, MlPluginStart>;
export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
private appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({}));
private urlGenerator: undefined | UrlGeneratorContract<typeof ML_APP_URL_GENERATOR>;
private locator: undefined | MlLocator;
constructor(private initializerContext: PluginInitializerContext) {}
@ -128,7 +123,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
});
if (pluginsSetup.share) {
this.urlGenerator = registerUrlGenerator(pluginsSetup.share, core);
this.locator = pluginsSetup.share.url.locators.create(new MlLocatorDefinition());
}
if (pluginsSetup.management) {
@ -179,7 +174,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
});
return {
urlGenerator: this.urlGenerator,
locator: this.locator,
};
}
@ -192,7 +187,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
});
return {
urlGenerator: this.urlGenerator,
locator: this.locator,
};
}

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import type { AppDeepLink } from 'src/core/public';
import { ML_PAGES } from '../../../common/constants/ml_url_generator';
import { ML_PAGES } from '../../../common/constants/locator';
const OVERVIEW_LINK_DEEP_LINK: AppDeepLink = {
id: 'mlOverviewDeepLink',

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import { createAction } from '../../../../../src/plugins/ui_actions/public';
import { MlCoreSetup } from '../plugin';
import { ML_APP_URL_GENERATOR } from '../../common/constants/ml_url_generator';
import { ML_APP_LOCATOR } from '../../common/constants/locator';
import {
ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE,
ANOMALY_SWIMLANE_EMBEDDABLE_TYPE,
@ -18,7 +18,7 @@ import {
SwimLaneDrilldownContext,
} from '../embeddables';
import { ENTITY_FIELD_OPERATIONS } from '../../common/util/anomaly_utils';
import { ExplorerAppState } from '../../common/types/ml_url_generator';
import { ExplorerAppState } from '../../common/types/locator';
export const OPEN_IN_ANOMALY_EXPLORER_ACTION = 'openInAnomalyExplorerAction';
@ -36,7 +36,7 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta
},
async getHref(context): Promise<string | undefined> {
const [, pluginsStart] = await getStartServices();
const urlGenerator = pluginsStart.share.urlGenerators.getUrlGenerator(ML_APP_URL_GENERATOR);
const locator = pluginsStart.share.url.locators.get(ML_APP_LOCATOR)!;
if (isSwimLaneEmbeddable(context)) {
const { embeddable, data } = context;
@ -44,7 +44,7 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta
const { jobIds, timeRange, viewBy } = embeddable.getInput();
const { perPage, fromPage } = embeddable.getOutput();
return urlGenerator.createUrl({
return locator.getUrl({
page: 'explorer',
pageState: {
jobIds,
@ -98,7 +98,7 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta
};
}
}
return urlGenerator.createUrl({
return locator.getUrl({
page: 'explorer',
pageState: {
jobIds,

View file

@ -11,7 +11,8 @@ import { mockAnomalies } from '../mock';
import { cloneDeep } from 'lodash/fp';
import { ExplorerLink } from './create_explorer_link';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public/context';
import { MlUrlGenerator } from '../../../../../../ml/public/ml_url_generator';
import { MlLocatorDefinition } from '../../../../../../ml/public/locator';
import { MockUrlService } from '../../../../../../../../src/plugins/share/common/mocks';
describe('create_explorer_link', () => {
let anomalies = cloneDeep(mockAnomalies);
@ -21,7 +22,9 @@ describe('create_explorer_link', () => {
});
test('it returns expected link', async () => {
const ml = { urlGenerator: new MlUrlGenerator({ appBasePath: '/app/ml', useHash: false }) };
const urlService = new MockUrlService();
const locator = urlService.locators.create(new MlLocatorDefinition());
const ml = { locator };
const http = { basePath: { get: jest.fn(() => {}) } };
await act(async () => {

View file

@ -37,8 +37,9 @@ import {
} from '../../../../common/constants';
import { StartServices } from '../../../types';
import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage';
import { MlUrlGenerator } from '../../../../../ml/public';
import { MlLocatorDefinition } from '../../../../../ml/public';
import { EuiTheme } from '../../../../../../../src/plugins/kibana_react/common';
import { MockUrlService } from 'src/plugins/share/common/mocks';
const mockUiSettings: Record<string, unknown> = {
[DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' },
@ -93,6 +94,8 @@ export const createStartServicesMock = (): StartServices => {
const { storage } = createSecuritySolutionStorageMock();
const data = dataPluginMock.createStartContract();
const security = securityMock.createSetup();
const urlService = new MockUrlService();
const locator = urlService.locators.create(new MlLocatorDefinition());
return ({
...core,
@ -139,10 +142,7 @@ export const createStartServicesMock = (): StartServices => {
security,
storage,
ml: {
urlGenerator: new MlUrlGenerator({
appBasePath: '/app/ml',
useHash: false,
}),
locator,
},
} as unknown) as StartServices;
};