Skip to content

Commit

Permalink
[7.x][ML] Migrate machine learning URLs to BrowserRouter format for A…
Browse files Browse the repository at this point in the history
…PM, Security, and Infra (elastic#78209) (elastic#79487)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
qn895 and elasticmachine committed Oct 5, 2020
1 parent fcc6e0e commit 2876807
Show file tree
Hide file tree
Showing 28 changed files with 350 additions and 186 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ import { EuiCallOut, EuiButton } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { useMlHref } from '../../../../../../ml/public';

export function LegacyJobsCallout() {
const { core } = useApmPluginContext();
const {
core,
plugins: { ml },
} = useApmPluginContext();
const mlADLink = useMlHref(ml, core.http.basePath.get(), {
page: 'jobs',
pageState: {
jobId: 'high_mean_response_time',
},
});

return (
<EuiCallOut
title={i18n.translate(
Expand All @@ -28,11 +39,7 @@ export function LegacyJobsCallout() {
}
)}
</p>
<EuiButton
href={core.http.basePath.prepend(
'/app/ml#/jobs?mlManagement=(jobId:high_mean_response_time)'
)}
>
<EuiButton href={mlADLink}>
{i18n.translate(
'xpack.apm.settings.anomaly_detection.legacy_jobs.button',
{ defaultMessage: 'Review jobs' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('MLJobLink', () => {
);

expect(href).toMatchInlineSnapshot(
`"/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now-4h))"`
`"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(mlTimeSeriesExplorer:(),zoom:(from:now%2Fw,to:now-4h))"`
);
});
it('should produce the correct URL with jobId, serviceName, and transactionType', async () => {
Expand All @@ -41,7 +41,27 @@ describe('MLJobLink', () => {
);

expect(href).toMatchInlineSnapshot(
`"/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now-4h))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-test,transaction.type:request)))"`
`"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-test,transaction.type:request)),zoom:(from:now%2Fw,to:now-4h))"`
);
});

it('correctly encodes time range values', async () => {
const href = await getRenderedHref(
() => (
<MLJobLink
jobId="apm-production-485b-high_mean_transaction_duration"
serviceName="opbeans-java"
transactionType="request"
/>
),
{
search:
'?rangeFrom=2020-07-29T17:27:29.000Z&rangeTo=2020-07-29T18:45:00.000Z&refreshInterval=10000&refreshPaused=true',
} as Location
);

expect(href).toMatchInlineSnapshot(
`"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(apm-production-485b-high_mean_transaction_duration)),refreshInterval:(pause:!t,value:10000),time:(from:'2020-07-29T17:27:29.000Z',to:'2020-07-29T18:45:00.000Z'))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-java,transaction.type:request)),zoom:(from:'2020-07-29T17:27:29.000Z',to:'2020-07-29T18:45:00.000Z'))"`
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import { MLLink } from './MLLink';

test('MLLink produces the correct URL', async () => {
const href = await getRenderedHref(
() => (
<MLLink path="/some/path" query={{ ml: { jobIds: ['something'] } }} />
),
() => <MLLink query={{ ml: { jobIds: ['something'] } }} />,
{
search:
'?rangeFrom=now-5h&rangeTo=now-2h&refreshPaused=true&refreshInterval=0',
} as Location
);

expect(href).toMatchInlineSnapshot(
`"/basepath/app/ml#/some/path?_g=(ml:(jobIds:!(something)),refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))&mlManagement=(groupIds:!(apm))"`
`"/app/ml/jobs?mlManagement=(groupIds:!(apm),jobId:!(something))&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"`
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@

import { EuiLink } from '@elastic/eui';
import React from 'react';
import { useLocation } from 'react-router-dom';
import rison, { RisonValue } from 'rison-node';
import url from 'url';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { getTimepickerRisonData, TimepickerRisonData } from '../rison_helpers';
import { useMlHref, ML_PAGES } from '../../../../../../ml/public';
import { useUrlParams } from '../../../../hooks/useUrlParams';

interface MlRisonData {
ml?: {
Expand All @@ -26,28 +24,41 @@ interface Props {
}

export function MLLink({ children, path = '', query = {}, external }: Props) {
const { core } = useApmPluginContext();
const location = useLocation();
const {
core,
plugins: { ml },
} = useApmPluginContext();

const risonQuery: MlRisonData & TimepickerRisonData = getTimepickerRisonData(
location.search
);

if (query.ml) {
risonQuery.ml = query.ml;
let jobIds: string[] = [];
if (query.ml?.jobIds) {
jobIds = query.ml.jobIds;
}
const { urlParams } = useUrlParams();
const { rangeFrom, rangeTo, refreshInterval, refreshPaused } = urlParams;

const href = url.format({
pathname: core.http.basePath.prepend('/app/ml'),
hash: `${path}?_g=${rison.encode(
risonQuery as RisonValue
)}&mlManagement=${rison.encode({ groupIds: ['apm'] })}`,
// default to link to ML Anomaly Detection jobs management page
const mlADLink = useMlHref(ml, core.http.basePath.get(), {
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
jobId: jobIds,
groupIds: ['apm'],
globalState: {
time:
rangeFrom !== undefined && rangeTo !== undefined
? { from: rangeFrom, to: rangeTo }
: undefined,
refreshInterval:
refreshPaused !== undefined && refreshInterval !== undefined
? { pause: refreshPaused, value: refreshInterval }
: undefined,
},
},
});

return (
<EuiLink
children={children}
href={href}
href={mlADLink}
external={external}
target={external ? '_blank' : undefined}
/>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import querystring from 'querystring';
import { useLocation } from 'react-router-dom';
import rison from 'rison-node';
import url from 'url';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { getTimepickerRisonData } from '../rison_helpers';
import { useMlHref } from '../../../../../../ml/public';
import { useUrlParams } from '../../../../hooks/useUrlParams';

export function useTimeSeriesExplorerHref({
jobId,
Expand All @@ -20,41 +17,38 @@ export function useTimeSeriesExplorerHref({
serviceName?: string;
transactionType?: string;
}) {
const { core } = useApmPluginContext();
const location = useLocation();
const { time, refreshInterval } = getTimepickerRisonData(location.search);
// default to link to ML Anomaly Detection jobs management page
const {
core,
plugins: { ml },
} = useApmPluginContext();
const { urlParams } = useUrlParams();
const { rangeFrom, rangeTo, refreshInterval, refreshPaused } = urlParams;

const search = querystring.stringify(
{
_g: rison.encode({
ml: { jobIds: [jobId] },
time,
refreshInterval,
}),
const timeRange =
rangeFrom !== undefined && rangeTo !== undefined
? { from: rangeFrom, to: rangeTo }
: undefined;
const mlAnomalyDetectionHref = useMlHref(ml, core.http.basePath.get(), {
page: 'timeseriesexplorer',
pageState: {
jobIds: [jobId],
timeRange,
refreshInterval:
refreshPaused !== undefined && refreshInterval !== undefined
? { pause: refreshPaused, value: refreshInterval }
: undefined,
zoom: timeRange,
...(serviceName && transactionType
? {
_a: rison.encode({
mlTimeSeriesExplorer: {
entities: {
'service.name': serviceName,
'transaction.type': transactionType,
},
},
}),
entities: {
'service.name': serviceName,
'transaction.type': transactionType,
},
}
: null),
: {}),
},
undefined,
undefined,
{
encodeURIComponent(str: string) {
return str;
},
}
);

return url.format({
pathname: core.http.basePath.prepend('/app/ml'),
hash: url.format({ pathname: '/timeseriesexplorer', search }),
});

return mlAnomalyDetectionHref;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ApmPluginContext, ApmPluginContextValue } from '.';
import { ConfigSchema } from '../..';
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
import { createCallApmApi } from '../../services/rest/createCallApmApi';
import { MlUrlGenerator } from '../../../../ml/public';

const uiSettings: Record<string, unknown> = {
[UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [
Expand Down Expand Up @@ -54,6 +55,7 @@ const mockCore = {
http: {
basePath: {
prepend: (path: string) => `/basepath${path}`,
get: () => `/basepath`,
},
},
i18n: {
Expand All @@ -78,10 +80,18 @@ const mockConfig: ConfigSchema = {
},
};

const mockPlugin = {
ml: {
urlGenerator: new MlUrlGenerator({
appBasePath: '/app/ml',
useHash: false,
}),
},
};
export const mockApmPluginContextValue = {
config: mockConfig,
core: mockCore,
plugins: {},
plugins: mockPlugin,
};

export function MockApmPluginContextWrapper({
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/apm/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ import { featureCatalogueEntry } from './featureCatalogueEntry';
import { toggleAppLinkInNav } from './toggleAppLinkInNav';
import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { registerApmAlerts } from './components/alerting/register_apm_alerts';
import { MlPluginSetup, MlPluginStart } from '../../ml/public';

export type ApmPluginSetup = void;
export type ApmPluginStart = void;

export interface ApmPluginSetupDeps {
alerts?: AlertingPluginPublicSetup;
ml?: MlPluginSetup;
data: DataPublicPluginSetup;
features: FeaturesPluginSetup;
home?: HomePublicPluginSetup;
Expand All @@ -52,6 +54,7 @@ export interface ApmPluginSetupDeps {

export interface ApmPluginStartDeps {
alerts?: AlertingPluginPublicStart;
ml?: MlPluginStart;
data: DataPublicPluginStart;
home: void;
licensing: void;
Expand Down
Loading

0 comments on commit 2876807

Please sign in to comment.