Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -126,6 +126,10 @@ export class ResultsLoader {
this._results$.next(this._results);
}

public get results$() {
return this._results$;
}

public subscribeToResults(func: ResultsSubscriber) {
return this._results$.subscribe(func);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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, { FC, useContext, useEffect, useState, useMemo, useCallback } from 'react';
import { EuiBasicTable, EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { from } from 'rxjs';
import { switchMap, takeWhile, tap } from 'rxjs/operators';
import { JobCreatorContext } from '../../../job_creator_context';
import { CategorizationJobCreator } from '../../../../../common/job_creator';
import { ml } from '../../../../../../../services/ml_api_service';
import { extractErrorProperties } from '../../../../../../../../../common/util/errors';

const NUMBER_OF_PREVIEW = 5;
export const CategoryStoppedPartitions: FC = () => {
const { jobCreator: jc, resultsLoader } = useContext(JobCreatorContext);
const jobCreator = jc as CategorizationJobCreator;
const [tableRow, setTableRow] = useState<Array<{ partitionName: string }>>([]);
const [stoppedPartitionsError, setStoppedPartitionsError] = useState<string | undefined>();

const columns = useMemo(
() => [
{
field: 'partitionName',
name: i18n.translate(
'xpack.ml.newJob.wizard.pickFieldsStep.stoppedPartitionsPreviewColumnName',
{
defaultMessage: 'Stopped partition names',
}
),
render: (partition: any) => (
<EuiText size="s">
<code>{partition}</code>
</EuiText>
),
},
],
[]
);

const loadCategoryStoppedPartitions = useCallback(async () => {
try {
const { jobs } = await ml.results.getCategoryStoppedPartitions([jobCreator.jobId]);

if (
!Array.isArray(jobs) && // if jobs is object of jobId: [partitions]
Array.isArray(jobs[jobCreator.jobId]) &&
jobs[jobCreator.jobId].length > 0
) {
return jobs[jobCreator.jobId];
}
} catch (e) {
const error = extractErrorProperties(e);
// might get 404 because job has not been created yet and that's ok
if (error.statusCode !== 404) {
setStoppedPartitionsError(error.message);
}
}
}, [jobCreator.jobId]);

useEffect(() => {
// only need to run this check if jobCreator.perPartitionStopOnWarn is turned on
if (jobCreator.perPartitionCategorization && jobCreator.perPartitionStopOnWarn) {
// subscribe to result updates
const resultsSubscription = resultsLoader.results$
.pipe(
switchMap(() => {
return from(loadCategoryStoppedPartitions());
Copy link
Contributor

Choose a reason for hiding this comment

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

loadCategoryStoppedPartitions depends on jobCreator.jobId, but your useEffect is only executed once, hence it only gets a reference of the initial callback. It could lead to unexpected behavior.

Copy link
Member

Choose a reason for hiding this comment

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

from my testing jobId is always correct here as this component is recreated when navigating to the summary step and the jobId cannot be changed by the user without navigating away to a previous step.

}),
tap((results) => {
if (Array.isArray(results)) {
setTableRow(
results.slice(0, NUMBER_OF_PREVIEW).map((partitionName) => ({
partitionName,
}))
);
}
}),
takeWhile((results) => {
return !results || (Array.isArray(results) && results.length <= NUMBER_OF_PREVIEW);
})
Comment on lines +83 to +85
Copy link
Contributor

Choose a reason for hiding this comment

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

When I shared a pseudo-code example I missed one point - takeWhile operator should be placed before switchMap where you perform stopped partitions fetching. With a current implementation potentially the stream can perform an unnecessary API call. It is not critical, but worth mentioning. 🙂 Can be improved later.

)
.subscribe();
return () => resultsSubscription.unsubscribe();
}
}, []);

return (
<>
{stoppedPartitionsError && (
<>
<EuiSpacer />
<EuiCallOut
color={'danger'}
size={'s'}
title={
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.stoppedPartitionsErrorCallout"
defaultMessage="An error occurred while fetching list of stopped partitions."
/>
}
/>
</>
)}
{Array.isArray(tableRow) && tableRow.length > 0 && (
<>
<EuiSpacer />
<div>
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.categorizationStoppedPartitionsTitle"
defaultMessage="Stopped partitions"
/>
</div>
<EuiSpacer size={'s'} />
<EuiCallOut
color={'warning'}
size={'s'}
title={
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.stoppedPartitionsExistCallout"
defaultMessage="Per-partition categorization and stop_on_warn settings are enabled. Some partitions in job '{jobId}' are unsuitable for categorization and have been excluded from further categorization or anomaly detection analysis."
values={{
jobId: jobCreator.jobId,
}}
/>
}
/>
<EuiBasicTable columns={columns} items={tableRow} />
</>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Results, Anomaly } from '../../../../../common/results_loader';
import { LineChartPoint } from '../../../../../common/chart_loader';
import { EventRateChart } from '../../../charts/event_rate_chart';
import { TopCategories } from './top_categories';
import { CategoryStoppedPartitions } from './category_stopped_partitions';

const DTR_IDX = 0;

Expand Down Expand Up @@ -73,6 +74,7 @@ export const CategorizationDetectorsSummary: FC = () => {
fadeChart={jobIsRunning}
/>
<TopCategories />
<CategoryStoppedPartitions />
</>
);
};