Skip to content

Commit a925b9b

Browse files
authored
[7.x] [Logs UI] Interpret finished analysis jobs as healthy (#… (#45353)
Backports the following commits to 7.x: - [Logs UI] Interpret finished analysis jobs as healthy (#45268)
1 parent 81989e5 commit a925b9b

File tree

2 files changed

+114
-71
lines changed

2 files changed

+114
-71
lines changed

x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,28 +34,33 @@ export const fetchJobStatusRequestPayloadRT = rt.type({
3434

3535
export type FetchJobStatusRequestPayload = rt.TypeOf<typeof fetchJobStatusRequestPayloadRT>;
3636

37-
// TODO: Get this to align with the payload - something is tripping it up somewhere
38-
// export const fetchJobStatusResponsePayloadRT = rt.array(rt.type({
39-
// datafeedId: rt.string,
40-
// datafeedIndices: rt.array(rt.string),
41-
// datafeedState: rt.string,
42-
// description: rt.string,
43-
// earliestTimestampMs: rt.number,
44-
// groups: rt.array(rt.string),
45-
// hasDatafeed: rt.boolean,
46-
// id: rt.string,
47-
// isSingleMetricViewerJob: rt.boolean,
48-
// jobState: rt.string,
49-
// latestResultsTimestampMs: rt.number,
50-
// latestTimestampMs: rt.number,
51-
// memory_status: rt.string,
52-
// nodeName: rt.union([rt.string, rt.undefined]),
53-
// processed_record_count: rt.number,
54-
// fullJob: rt.any,
55-
// auditMessage: rt.any,
56-
// deleting: rt.union([rt.boolean, rt.undefined]),
57-
// }));
58-
59-
export const fetchJobStatusResponsePayloadRT = rt.any;
37+
const datafeedStateRT = rt.keyof({
38+
started: null,
39+
stopped: null,
40+
});
41+
42+
const jobStateRT = rt.keyof({
43+
closed: null,
44+
closing: null,
45+
failed: null,
46+
opened: null,
47+
opening: null,
48+
});
49+
50+
export const jobSummaryRT = rt.intersection([
51+
rt.type({
52+
id: rt.string,
53+
jobState: jobStateRT,
54+
}),
55+
rt.partial({
56+
datafeedIndices: rt.array(rt.string),
57+
datafeedState: datafeedStateRT,
58+
fullJob: rt.partial({
59+
finished_time: rt.number,
60+
}),
61+
}),
62+
]);
63+
64+
export const fetchJobStatusResponsePayloadRT = rt.array(jobSummaryRT);
6065

6166
export type FetchJobStatusResponsePayload = rt.TypeOf<typeof fetchJobStatusResponsePayloadRT>;

x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx

Lines changed: 86 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,23 @@
55
*/
66

77
import createContainer from 'constate-latest';
8-
import { useMemo, useEffect, useState } from 'react';
9-
import { bucketSpan, getJobId } from '../../../../common/log_analysis';
8+
import { useEffect, useMemo, useState } from 'react';
9+
10+
import { bucketSpan, getDatafeedId, getJobId, JobType } from '../../../../common/log_analysis';
1011
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
12+
import { callJobsSummaryAPI, FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api';
1113
import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_setup_module_api';
12-
import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api';
1314

1415
// combines and abstracts job and datafeed status
1516
type JobStatus =
1617
| 'unknown'
1718
| 'missing'
18-
| 'inconsistent'
19-
| 'created'
19+
| 'initializing'
20+
| 'stopped'
2021
| 'started'
21-
| 'opening'
22-
| 'opened'
22+
| 'finished'
2323
| 'failed';
2424

25-
interface AllJobStatuses {
26-
[key: string]: JobStatus;
27-
}
28-
29-
const getInitialJobStatuses = (): AllJobStatuses => {
30-
return {
31-
logEntryRate: 'unknown',
32-
};
33-
};
34-
3525
export const useLogAnalysisJobs = ({
3626
indexPattern,
3727
sourceId,
@@ -43,14 +33,19 @@ export const useLogAnalysisJobs = ({
4333
spaceId: string;
4434
timeField: string;
4535
}) => {
46-
const [jobStatus, setJobStatus] = useState<AllJobStatuses>(getInitialJobStatuses());
36+
const [jobStatus, setJobStatus] = useState<Record<JobType, JobStatus>>({
37+
'log-entry-rate': 'unknown',
38+
});
4739
const [hasCompletedSetup, setHasCompletedSetup] = useState<boolean>(false);
4840

4941
const [setupMlModuleRequest, setupMlModule] = useTrackedPromise(
5042
{
5143
cancelPreviousOn: 'resolution',
5244
createPromise: async (start, end) => {
53-
setJobStatus(getInitialJobStatuses());
45+
setJobStatus(currentJobStatus => ({
46+
...currentJobStatus,
47+
'log-entry-rate': 'initializing',
48+
}));
5449
return await callSetupMlModuleAPI(
5550
start,
5651
end,
@@ -62,26 +57,25 @@ export const useLogAnalysisJobs = ({
6257
);
6358
},
6459
onResolve: ({ datafeeds, jobs }: SetupMlModuleResponsePayload) => {
65-
const hasSuccessfullyCreatedJobs = jobs.every(job => job.success);
66-
const hasSuccessfullyStartedDatafeeds = datafeeds.every(
67-
datafeed => datafeed.success && datafeed.started
68-
);
69-
const hasAnyErrors =
70-
jobs.some(job => !!job.error) || datafeeds.some(datafeed => !!datafeed.error);
71-
7260
setJobStatus(currentJobStatus => ({
7361
...currentJobStatus,
74-
logEntryRate: hasAnyErrors
75-
? 'failed'
76-
: hasSuccessfullyCreatedJobs
77-
? hasSuccessfullyStartedDatafeeds
62+
'log-entry-rate':
63+
hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobs) &&
64+
hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, 'log-entry-rate'))(
65+
datafeeds
66+
)
7867
? 'started'
79-
: 'failed'
80-
: 'failed',
68+
: 'failed',
8169
}));
8270

8371
setHasCompletedSetup(true);
8472
},
73+
onReject: () => {
74+
setJobStatus(currentJobStatus => ({
75+
...currentJobStatus,
76+
'log-entry-rate': 'failed',
77+
}));
78+
},
8579
},
8680
[indexPattern, spaceId, sourceId]
8781
);
@@ -90,20 +84,19 @@ export const useLogAnalysisJobs = ({
9084
{
9185
cancelPreviousOn: 'resolution',
9286
createPromise: async () => {
93-
return callJobsSummaryAPI(spaceId, sourceId);
87+
return await callJobsSummaryAPI(spaceId, sourceId);
9488
},
9589
onResolve: response => {
96-
if (response && response.length) {
97-
const logEntryRate = response.find(
98-
(job: any) => job.id === getJobId(spaceId, sourceId, 'log-entry-rate')
99-
);
100-
setJobStatus({
101-
logEntryRate: logEntryRate ? logEntryRate.jobState : 'unknown',
102-
});
103-
}
90+
setJobStatus(currentJobStatus => ({
91+
...currentJobStatus,
92+
'log-entry-rate': getJobStatus(getJobId(spaceId, sourceId, 'log-entry-rate'))(response),
93+
}));
10494
},
105-
onReject: error => {
106-
// TODO: Handle errors
95+
onReject: err => {
96+
setJobStatus(currentJobStatus => ({
97+
...currentJobStatus,
98+
'log-entry-rate': 'unknown',
99+
}));
107100
},
108101
},
109102
[indexPattern, spaceId, sourceId]
@@ -114,11 +107,7 @@ export const useLogAnalysisJobs = ({
114107
}, []);
115108

116109
const isSetupRequired = useMemo(() => {
117-
const jobStates = Object.values(jobStatus);
118-
return (
119-
jobStates.filter(state => ['opened', 'opening', 'created', 'started'].includes(state))
120-
.length < jobStates.length
121-
);
110+
return !Object.values(jobStatus).every(state => ['started', 'finished'].includes(state));
122111
}, [jobStatus]);
123112

124113
const isLoadingSetupStatus = useMemo(() => fetchJobStatusRequest.state === 'pending', [
@@ -147,3 +136,52 @@ export const useLogAnalysisJobs = ({
147136
};
148137

149138
export const LogAnalysisJobs = createContainer(useLogAnalysisJobs);
139+
140+
const hasSuccessfullyCreatedJob = (jobId: string) => (
141+
jobSetupResponses: SetupMlModuleResponsePayload['jobs']
142+
) =>
143+
jobSetupResponses.filter(
144+
jobSetupResponse =>
145+
jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error
146+
).length > 0;
147+
148+
const hasSuccessfullyStartedDatafeed = (datafeedId: string) => (
149+
datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds']
150+
) =>
151+
datafeedSetupResponses.filter(
152+
datafeedSetupResponse =>
153+
datafeedSetupResponse.id === datafeedId &&
154+
datafeedSetupResponse.success &&
155+
datafeedSetupResponse.started &&
156+
!datafeedSetupResponse.error
157+
).length > 0;
158+
159+
const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePayload): JobStatus =>
160+
jobSummaries
161+
.filter(jobSummary => jobSummary.id === jobId)
162+
.map(
163+
(jobSummary): JobStatus => {
164+
if (jobSummary.jobState === 'failed') {
165+
return 'failed';
166+
} else if (
167+
jobSummary.jobState === 'closed' &&
168+
jobSummary.datafeedState === 'stopped' &&
169+
jobSummary.fullJob &&
170+
jobSummary.fullJob.finished_time != null
171+
) {
172+
return 'finished';
173+
} else if (
174+
jobSummary.jobState === 'closed' ||
175+
jobSummary.jobState === 'closing' ||
176+
jobSummary.datafeedState === 'stopped'
177+
) {
178+
return 'stopped';
179+
} else if (jobSummary.jobState === 'opening') {
180+
return 'initializing';
181+
} else if (jobSummary.jobState === 'opened' && jobSummary.datafeedState === 'started') {
182+
return 'started';
183+
}
184+
185+
return 'unknown';
186+
}
187+
)[0] || 'missing';

0 commit comments

Comments
 (0)