Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Persisted URL state for the "Anomaly detection jobs" page #83149

Merged
merged 11 commits into from
Nov 12, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ test('MLLink produces the correct URL', async () => {
);

expect(href).toMatchInlineSnapshot(
`"/app/ml/jobs?mlManagement=(groupIds:!(apm),jobId:!(something))&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"`
`"/app/ml/jobs?_a=(queryText:'id:(something)%20groups:(apm)')&_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,10 +6,11 @@

import { EuiCard, EuiIcon, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { SetupStatus } from '../../../../../common/log_analysis';
import { CreateJobButton, RecreateJobButton } from '../../log_analysis_setup/create_job_button';
import { useLinkProps } from '../../../../hooks/use_link_props';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { mountReactNode } from '../../../../../../../../src/core/public/utils';

export const LogAnalysisModuleListCard: React.FC<{
jobId: string;
Expand All @@ -26,19 +27,46 @@ export const LogAnalysisModuleListCard: React.FC<{
moduleStatus,
onViewSetup,
}) => {
const {
services: {
ml,
application: { navigateToUrl },
notifications: { toasts },
},
} = useKibanaContextForPlugin();

const [viewInMlLink, setViewInMlLink] = useState<string>('');

const getMlUrl = async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can skip this check. If the ML module is not available I don't think the setup flyout will ever be mounted anyway.

if (!ml.urlGenerator) {
toasts.addWarning({
title: mountReactNode(
<FormattedMessage
id="xpack.infra.logs.analysis.mlNotAvailable"
defaultMessage="ML plugin is not available"
/>
),
});
return;
}
setViewInMlLink(await ml.urlGenerator.createUrl({ page: 'jobs', pageState: { jobId } }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR but I'm curious: how come the createUrl() method is async? Does it fetch anything in the background to generate the URL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a question for the Kibana team. Check the exposed public contract of the URL generator

createUrl: async (state: UrlGeneratorStateMapping[Id]['State']) => {

};

useEffect(() => {
getMlUrl();
});

const navigateToMlApp = async () => {
await navigateToUrl(viewInMlLink);
};

const moduleIcon =
moduleStatus.type === 'required' ? (
<EuiIcon size="xxl" type="machineLearningApp" />
) : (
<EuiIcon color="secondary" size="xxl" type="check" />
);

const viewInMlLinkProps = useLinkProps({
app: 'ml',
pathname: '/jobs',
search: { mlManagement: `(jobId:${jobId})` },
});

const moduleSetupButton =
moduleStatus.type === 'required' ? (
<CreateJobButton hasSetupCapabilities={hasSetupCapabilities} onClick={onViewSetup}>
Expand All @@ -50,13 +78,17 @@ export const LogAnalysisModuleListCard: React.FC<{
) : (
<>
<RecreateJobButton hasSetupCapabilities={hasSetupCapabilities} onClick={onViewSetup} />
<EuiSpacer size="xs" />
<EuiButtonEmpty {...viewInMlLinkProps}>
<FormattedMessage
id="xpack.infra.logs.analysis.viewInMlButtonLabel"
defaultMessage="View in Machine Learning"
/>
</EuiButtonEmpty>
{viewInMlLink ? (
<>
<EuiSpacer size="xs" />
<EuiButtonEmpty onClick={navigateToMlApp}>
<FormattedMessage
id="xpack.infra.logs.analysis.viewInMlButtonLabel"
defaultMessage="View in Machine Learning"
/>
</EuiButtonEmpty>
</>
) : null}
</>
);

Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/infra/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
ObservabilityPluginStart,
} from '../../observability/public';
import type { SpacesPluginStart } from '../../spaces/public';
import { MlPluginStart } from '../../ml/public';

// Our own setup and start contract values
export type InfraClientSetupExports = void;
Expand All @@ -38,6 +39,7 @@ export interface InfraClientStartDeps {
spaces: SpacesPluginStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
usageCollection: UsageCollectionStart;
ml: MlPluginStart;
}

export type InfraClientCoreSetup = CoreSetup<InfraClientStartDeps, InfraClientStartExports>;
Expand Down
22 changes: 21 additions & 1 deletion x-pack/plugins/ml/common/util/string_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { renderTemplate, getMedianStringLength, stringHash } from './string_utils';
import {
renderTemplate,
getMedianStringLength,
stringHash,
getGroupQueryText,
} from './string_utils';

const strings: string[] = [
'foo',
Expand Down Expand Up @@ -54,4 +59,19 @@ describe('ML - string utils', () => {
expect(hash1).not.toBe(hash2);
});
});

describe('getGroupQueryText', () => {
const groupIdOne = 'test_group_id_1';
const groupIdTwo = 'test_group_id_2';

it('should get query string for selected group ids', () => {
const actual = getGroupQueryText([groupIdOne, groupIdTwo]);
expect(actual).toBe(`groups:(${groupIdOne} or ${groupIdTwo})`);
});

it('should get query string for selected group id', () => {
const actual = getGroupQueryText([groupIdOne]);
expect(actual).toBe(`groups:(${groupIdOne})`);
});
});
});
8 changes: 8 additions & 0 deletions x-pack/plugins/ml/common/util/string_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ export function stringHash(str: string): number {
}
return hash < 0 ? hash * -2 : hash;
}

export function getGroupQueryText(groupIds: string[]): string {
return `groups:(${groupIds.join(' or ')})`;
}

export function getJobQueryText(jobIds: string | string[]): string {
return Array.isArray(jobIds) ? `id:(${jobIds.join(' OR ')})` : jobIds;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ import { getTaskStateBadge, getJobTypeBadge, useColumns } from './use_columns';
import { ExpandedRow } from './expanded_row';
import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar';
import { CreateAnalyticsButton } from '../create_analytics_button';
import {
getSelectedIdFromUrl,
getGroupQueryText,
} from '../../../../../jobs/jobs_list/components/utils';
import { getSelectedIdFromUrl } from '../../../../../jobs/jobs_list/components/utils';
import { SourceSelection } from '../source_selection';
import { filterAnalytics } from '../../../../common/search_bar_filters';
import { AnalyticsEmptyPrompt } from './empty_prompt';
import { useTableSettings } from './use_table_settings';
import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button';
import { getGroupQueryText } from '../../../../../../../common/util/string_utils';

const filters: EuiSearchBarProps['filters'] = [
{
Expand Down

This file was deleted.

Loading