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

[Logs UI] Create screen to set up analysis ML jobs #43413

Merged
merged 32 commits into from
Aug 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
de0499e
Add empty analysis tab
weltenwort Aug 8, 2019
8d06a54
Merge branch 'master' into logs-ui-add-analysis-tab
weltenwort Aug 8, 2019
0ba9cca
Add ml capabilities check
weltenwort Aug 8, 2019
84248c2
Add job status checking functionality
Kerry350 Aug 8, 2019
31be96e
Add a loading page for the job status check
Kerry350 Aug 9, 2019
6c0bac5
Change types / change method for deriving space ID / change setup req…
Kerry350 Aug 9, 2019
98a9fa9
Use new structure
Kerry350 Aug 9, 2019
83330d7
Add module setup to log analysis jobs hook
weltenwort Aug 9, 2019
916c167
Merge remote-tracking branch 'upstream/master' into logs-ui-add-ml-mo…
Kerry350 Aug 13, 2019
6951bde
Add ID to path
Kerry350 Aug 13, 2019
fe9c76a
Merge remote-tracking branch 'weltenwort/logs-ui-add-ml-module-setup-…
Zacqary Aug 15, 2019
705577a
[Logs UI] Add analyis setup landing screen
Zacqary Aug 15, 2019
1beb78c
Add function to set up ML module on click
Zacqary Aug 15, 2019
f6594e3
Use partial type for start and end props
Zacqary Aug 15, 2019
fb139df
Add start and end time selection
Zacqary Aug 15, 2019
c36586f
Fix syntax
Kerry350 Aug 16, 2019
2569444
Change seconds timestamp to ms
Zacqary Aug 16, 2019
c4a2c0a
Merge branch '41877-ml-setup-screen' of github.com:Zacqary/kibana int…
Zacqary Aug 16, 2019
fab784e
Update wording
Zacqary Aug 16, 2019
3104d10
Use FormControlLayout to clear datepickers
Zacqary Aug 16, 2019
7ec64cd
Update wording about earlier start date
Zacqary Aug 16, 2019
895cb3f
Remove specific point in time wording
Zacqary Aug 16, 2019
69d7a7b
Fix typechecking
Zacqary Aug 16, 2019
b20e018
Reload analysis page on successful job creation
Zacqary Aug 16, 2019
5ca200f
Add error handling for setup failure
Zacqary Aug 16, 2019
5bb9e35
Merge remote-tracking branch 'upstream/master' into 41877-ml-setup-sc…
Zacqary Aug 16, 2019
09efec9
Update description ton of feature to reflect 7.4 feature set
Zacqary Aug 19, 2019
4908922
Add toggleable default message
Zacqary Aug 19, 2019
c7a33dc
Revert to EuiFormControlLayout until eui changes are pushed
Zacqary Aug 19, 2019
4871bad
Merge branch 'master' of github.com:elastic/kibana into 41877-ml-setu…
Zacqary Aug 19, 2019
c13eb2f
Merge remote-tracking branch 'upstream/master' into 41877-ml-setup-sc…
Kerry350 Aug 22, 2019
41ca5e4
Remove sample data index if user has it set
Kerry350 Aug 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

import { JobType } from './log_analysis';

export const bucketSpan = 900000;

export const getJobIdPrefix = (spaceId: string, sourceId: string) =>
`kibana-logs-ui-${spaceId}-${sourceId}-`;

export const getJobId = (spaceId: string, sourceId: string, jobType: JobType) =>
`${getJobIdPrefix(spaceId, sourceId)}${jobType}`;

export const getDatafeedId = (spaceId: string, sourceId: string, jobType: JobType) =>
`datafeed-${getJobId(spaceId, sourceId, jobType)}`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* 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 * as rt from 'io-ts';
Copy link
Member

Choose a reason for hiding this comment

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

Can someone explain to me why io-ts gets called rt?

import { kfetch } from 'ui/kfetch';

import { getJobIdPrefix } from '../../../../../common/log_analysis';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';

const MODULE_ID = 'logs_ui_analysis';

// This is needed due to: https://github.com/elastic/kibana/issues/43671
const removeSampleDataIndex = (indexPattern: string) => {
const SAMPLE_DATA_INDEX = 'kibana_sample_data_logs*';
const indices = indexPattern.split(',');
const sampleDataIndex = indices.findIndex((index: string) => {
return index === SAMPLE_DATA_INDEX;
});
if (sampleDataIndex > -1) {
indices.splice(sampleDataIndex, 1);
return indices.join(',');
} else {
return indexPattern;
}
};
Copy link
Member

@jasonrhodes jasonrhodes Aug 22, 2019

Choose a reason for hiding this comment

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

Would this work with a filter() too? If so that might be easier to follow later when we want to remember what this does and safely rip it out ... maybe it's just me but I always forget how indices and -1 and Array#splice all work :P

return indices.filter(index => index !== SAMPLE_DATA_INDEX).join(',')

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 I'll change this on the "cleanup / bug fixing" PR so I don't need to trigger another build cycle.


export const callSetupMlModuleAPI = async (
start: number | undefined,
end: number | undefined,
spaceId: string,
sourceId: string,
indexPattern: string,
timeField: string,
bucketSpan: number
) => {
const response = await kfetch({
method: 'POST',
pathname: `/api/ml/modules/setup/${MODULE_ID}`,
body: JSON.stringify(
setupMlModuleRequestPayloadRT.encode({
start,
end,
indexPatternName: removeSampleDataIndex(indexPattern),
prefix: getJobIdPrefix(spaceId, sourceId),
startDatafeed: true,
jobOverrides: [
{
job_id: 'log-entry-rate',
analysis_config: {
bucket_span: `${bucketSpan}ms`,
},
data_description: {
time_field: timeField,
},
},
],
datafeedOverrides: [
{
job_id: 'log-entry-rate',
aggregations: {
buckets: {
date_histogram: {
field: timeField,
fixed_interval: `${bucketSpan}ms`,
},
aggregations: {
[timeField]: {
max: {
field: `${timeField}`,
},
},
doc_count_per_minute: {
bucket_script: {
script: {
params: {
bucket_span_in_ms: bucketSpan,
},
},
},
},
},
},
},
},
],
})
),
});

return setupMlModuleResponsePayloadRT.decode(response).getOrElseL(throwErrors(createPlainError));
};

const setupMlModuleTimeParamsRT = rt.partial({
start: rt.number,
end: rt.number,
});

const setupMlModuleRequestParamsRT = rt.type({
indexPatternName: rt.string,
prefix: rt.string,
startDatafeed: rt.boolean,
jobOverrides: rt.array(rt.object),
datafeedOverrides: rt.array(rt.object),
});

const setupMlModuleRequestPayloadRT = rt.intersection([
setupMlModuleTimeParamsRT,
setupMlModuleRequestParamsRT,
]);

const setupMlModuleResponsePayloadRT = rt.type({
datafeeds: rt.array(
rt.type({
id: rt.string,
started: rt.boolean,
success: rt.boolean,
})
),
jobs: rt.array(
rt.type({
id: rt.string,
success: rt.boolean,
})
),
});

export type SetupMlModuleResponsePayload = rt.TypeOf<typeof setupMlModuleResponsePayloadRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,70 @@

import createContainer from 'constate-latest';
import { useMemo, useEffect, useState } from 'react';
import { values } from 'lodash';
import { getJobId } from '../../../../common/log_analysis';
import { bucketSpan, getJobId } from '../../../../common/log_analysis';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_setup_module_api';
import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api';

type JobStatus = 'unknown' | 'closed' | 'closing' | 'failed' | 'opened' | 'opening' | 'deleted';
// type DatafeedStatus = 'unknown' | 'started' | 'starting' | 'stopped' | 'stopping' | 'deleted';
// combines and abstracts job and datafeed status
type JobStatus =
| 'unknown'
| 'missing'
| 'inconsistent'
| 'created'
| 'started'
| 'opening'
| 'opened';

export const useLogAnalysisJobs = ({
indexPattern,
sourceId,
spaceId,
timeField,
}: {
indexPattern: string;
sourceId: string;
spaceId: string;
timeField: string;
}) => {
const [jobStatus, setJobStatus] = useState<{
logEntryRate: JobStatus;
}>({
logEntryRate: 'unknown',
});

// const [setupMlModuleRequest, setupMlModule] = useTrackedPromise(
// {
// cancelPreviousOn: 'resolution',
// createPromise: async () => {
// kfetch({
// method: 'POST',
// pathname: '/api/ml/modules/setup',
// body: JSON.stringify(
// setupMlModuleRequestPayloadRT.encode({
// indexPatternName: indexPattern,
// prefix: getJobIdPrefix(spaceId, sourceId),
// startDatafeed: true,
// })
// ),
// });
// },
// },
// [indexPattern, spaceId, sourceId]
// );
const [setupMlModuleRequest, setupMlModule] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async (start, end) => {
return await callSetupMlModuleAPI(
start,
end,
spaceId,
sourceId,
indexPattern,
timeField,
bucketSpan
);
},
onResolve: ({ datafeeds, jobs }: SetupMlModuleResponsePayload) => {
const hasSuccessfullyCreatedJobs = jobs.every(job => job.success);
const hasSuccessfullyStartedDatafeeds = datafeeds.every(
datafeed => datafeed.success && datafeed.started
);

setJobStatus(currentJobStatus => ({
...currentJobStatus,
logEntryRate: hasSuccessfullyCreatedJobs
? hasSuccessfullyStartedDatafeeds
? 'started'
: 'created'
: 'inconsistent',
}));
},
},
[indexPattern, spaceId, sourceId]
);

const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise(
{
Expand Down Expand Up @@ -77,20 +99,34 @@ export const useLogAnalysisJobs = ({
}, []);

const isSetupRequired = useMemo(() => {
const jobStates = values(jobStatus);
const jobStates = Object.values(jobStatus);
return (
jobStates.filter(state => state === 'opened' || state === 'opening').length < jobStates.length
jobStates.filter(state => ['opened', 'opening', 'created', 'started'].includes(state))
.length < jobStates.length
);
}, [jobStatus]);

const isLoadingSetupStatus = useMemo(() => fetchJobStatusRequest.state === 'pending', [
fetchJobStatusRequest.state,
]);

const isSettingUpMlModule = useMemo(() => setupMlModuleRequest.state === 'pending', [
setupMlModuleRequest.state,
]);

const didSetupFail = useMemo(
() => !isSettingUpMlModule && setupMlModuleRequest.state !== 'uninitialized' && isSetupRequired,
[setupMlModuleRequest.state, jobStatus]
);

return {
jobStatus,
isSetupRequired,
isLoadingSetupStatus,
setupMlModule,
setupMlModuleRequest,
isSettingUpMlModule,
didSetupFail,
};
};

Expand Down
Loading