Skip to content

Commit

Permalink
[APM] "View job" link from latency charts leads to a malfunctioning p…
Browse files Browse the repository at this point in the history
…age (elastic#86788)

* fixing ML links

* fixing ML links
  • Loading branch information
cauemarcondes committed Dec 23, 2020
1 parent 1920f6a commit c379a90
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import {
EuiPanel,
EuiTitle,
EuiText,
EuiSpacer,
EuiButton,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { getEnvironmentLabel } from '../../../../../common/environment_filter_values';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable';
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
import { MLSingleMetricLink } from '../../../shared/Links/MachineLearningLinks/MLSingleMetricLink';
import { MLExplorerLink } from '../../../shared/Links/MachineLearningLinks/MLExplorerLink';
import { MLManageJobsLink } from '../../../shared/Links/MachineLearningLinks/MLManageJobsLink';
import { getEnvironmentLabel } from '../../../../../common/environment_filter_values';
import { LegacyJobsCallout } from './legacy_jobs_callout';
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable';
import { AnomalyDetectionApiResponse } from './index';
import { LegacyJobsCallout } from './legacy_jobs_callout';

type Jobs = AnomalyDetectionApiResponse['jobs'];

Expand All @@ -44,14 +44,14 @@ const columns: Array<ITableColumn<Jobs[0]>> = [
{ defaultMessage: 'Action' }
),
render: (jobId: string) => (
<MLSingleMetricLink jobId={jobId}>
<MLExplorerLink jobId={jobId}>
{i18n.translate(
'xpack.apm.settings.anomalyDetection.jobList.mlJobLinkText',
{
defaultMessage: 'View job in ML',
}
)}
</MLSingleMetricLink>
</MLExplorerLink>
),
},
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { Location } from 'history';
import React from 'react';
import { getRenderedHref } from '../../../../utils/testHelpers';
import { MLExplorerLink } from './MLExplorerLink';

describe('MLExplorerLink', () => {
it('should produce the correct URL with jobId', async () => {
const href = await getRenderedHref(
() => (
<MLExplorerLink jobId="myservicename-mytransactiontype-high_mean_response_time" />
),
{
search:
'?rangeFrom=now/w&rangeTo=now-4h&refreshPaused=true&refreshInterval=0',
} as Location
);

expect(href).toMatchInlineSnapshot(
`"/app/ml/explorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"`
);
});

it('correctly encodes time range values', async () => {
const href = await getRenderedHref(
() => (
<MLExplorerLink jobId="apm-production-485b-high_mean_transaction_duration" />
),
{
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/explorer?_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=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { ReactNode } from 'react';
import { EuiLink } from '@elastic/eui';
import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useMlHref, ML_PAGES } from '../../../../../../ml/public';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { TimePickerRefreshInterval } from '../../DatePicker/typings';

interface Props {
children?: ReactNode;
jobId: string;
external?: boolean;
}

export function MLExplorerLink({ jobId, external, children }: Props) {
const href = useExplorerHref({ jobId });

return (
<EuiLink
children={children}
href={href}
external={external}
target={external ? '_blank' : undefined}
/>
);
}

export function useExplorerHref({ jobId }: { jobId: string }) {
const {
core,
plugins: { ml },
} = useApmPluginContext();
const { urlParams } = useUrlParams();

const timePickerRefreshIntervalDefaults = core.uiSettings.get<TimePickerRefreshInterval>(
UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS
);

const {
// hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval
rangeFrom = 'now-1h',
rangeTo = 'now',
refreshInterval = timePickerRefreshIntervalDefaults.value,
refreshPaused = timePickerRefreshIntervalDefaults.pause,
} = urlParams;

const href = useMlHref(ml, core.http.basePath.get(), {
page: ML_PAGES.ANOMALY_EXPLORER,
pageState: {
jobIds: [jobId],
timeRange: { from: rangeFrom, to: rangeTo },
refreshInterval: { pause: refreshPaused, value: refreshInterval },
},
});

return href;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isEmpty } from 'lodash';
import React from 'react';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { MLSingleMetricLink } from '../../Links/MachineLearningLinks/MLSingleMetricLink';

Expand All @@ -33,12 +34,13 @@ const ShiftedEuiText = styled(EuiText)`
export function MLHeader({ hasValidMlLicense, mlJobId }: Props) {
const { serviceName } = useParams<{ serviceName?: string }>();
const { urlParams } = useUrlParams();
const { transactionType } = useApmServiceContext();

if (!hasValidMlLicense || !mlJobId) {
return null;
}

const { kuery, transactionType } = urlParams;
const { kuery } = urlParams;

const hasKuery = !isEmpty(kuery);
const icon = hasKuery ? (
Expand Down

0 comments on commit c379a90

Please sign in to comment.