-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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] Add support for per-partition categorization jobs #74592
Conversation
@@ -696,4 +699,166 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { | |||
} | |||
}) | |||
); | |||
|
|||
/** | |||
* @apiGroup AnomalyDetectors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these endpoints are better suited to job_service
, as this file only contains wrappers for the anomaly_detectors
es endpoints.
there is a categorisation section inside here: https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/server/models/job_service/new_job/categorization
but as these are used for results, they might need a different folder, or maybe should live in results_service
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. Now that I see it, I think it's probably more suitable for results_service. Will update it to be that group instead. Thanks Jame!
…r-partition # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
}, | ||
}); | ||
} | ||
const results: SearchResponse<AnomalyCategorizerStatsDoc> = await callAsCurrentUser('search', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all searches to ML_RESULTS_INDEX_PATTERN
should be performed by the internal user.
Pinging @elastic/ml-ui (:ml) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding API tests as part of this PR! 🎉
Left a few comments.
...fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
Outdated
Show resolved
Hide resolved
...ields_step/components/categorization_partition_field/categorization_per_partition_switch.tsx
Outdated
Show resolved
Hide resolved
...fields_step/components/categorization_partition_field/categorization_stop_on_warn_switch.tsx
Outdated
Show resolved
Hide resolved
x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts
Outdated
Show resolved
Hide resolved
x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts
Outdated
Show resolved
Hide resolved
x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts
Outdated
Show resolved
Hide resolved
…r-partition # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
Updated PR with the latest changes and I think it's ready for a final look @jgowdyelastic, @alvarezmelissa87, @pheyos 🙏 |
{stoppedPartitions && ( | ||
<EuiCallOut | ||
size={'s'} | ||
title={i18n.translate('xpack.ml.explorer.stoppedPartitionsExistCallout', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's recommended to use { FormattedMessage } from '@kbn/i18n/react';
inside of components instead of i18n
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated all the i18n.translate
to using FormattedMessage here 3686b73
@@ -57,6 +57,7 @@ export class JobCreator { | |||
private _stopAllRefreshPolls: { | |||
stop: boolean; | |||
} = { stop: false }; | |||
private _partitionField: string | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use
private _partitionField: string | null; | |
private _partitionField: string | null = null; |
and there is no need to defined it in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After discussing with James, we decided to remove this _partitionField altogether here 0a813e1
@@ -21,6 +27,13 @@ export const ExtraSettings: FC = () => { | |||
<SummaryCountField /> | |||
</EuiFlexItem> | |||
</EuiFlexGroup> | |||
{showCategorizationPerPartitionField && ( | |||
<EuiFlexGroup gutterSize="xl"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you need a flex group just for one item?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed here a94ecaf
CategorizationJobCreator, | ||
isCategorizationJobCreator, | ||
} from '../../../../../common/job_creator'; | ||
import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid using static imports for services. Put them in a context instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i agree this would be better in a context, however newJobCapsService
is used throughout the wizards and so i'd suggest this happens in a refactoring PR later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll save this issue for a follow up PR like James mentioned
const options: EuiComboBoxOptionOption[] = [ | ||
...createFieldOptions(fields, jobCreator.additionalFields), | ||
]; | ||
|
||
const selection: EuiComboBoxOptionOption[] = []; | ||
if (selectedField !== null) { | ||
selection.push({ label: selectedField }); | ||
} | ||
|
||
function onChange(selectedOptions: EuiComboBoxOptionOption[]) { | ||
const option = selectedOptions[0]; | ||
if (typeof option !== 'undefined') { | ||
changeHandler(option.label); | ||
} else { | ||
changeHandler(null); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It all gets invoked on each render. Please use hooks accordingly (useMemo
and useCallback
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here a94ecaf
} | ||
} catch (error) { | ||
// eslint-disable-next-line no-console | ||
console.error(error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to fail silently here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for this scenario it's okay to fail silently since it's only there to give a warning/info message if one of the partition has stopped. But if we think it's important for the user to know, I can add an error message.
}); | ||
|
||
if (!jobConfigResponse || jobConfigResponse.jobs.length < 1) { | ||
throw Error(`Unable to find anomaly detector jobs ${jobIds.join(', ')}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shall we use throw Boom.notFound
instead? to return an error with appropriate code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here a94ecaf
* @api {get} /api/ml/results/:jobId/categorizer_stats | ||
* @apiName GetStoppedPartitions | ||
* @apiDescription Returns list of partitions we stopped categorizing whens status changed to warn | ||
* @apiSchema (params) jobIdSchema |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be
* @apiSchema (params) jobIdSchema | |
* @apiSchema (body) getStoppedPartitionsSchema |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here a94ecaf
/** | ||
* @apiGroup ResultsService | ||
* | ||
* @api {get} /api/ml/results/:jobId/categorizer_stats |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be
* @api {get} /api/ml/results/:jobId/categorizer_stats | |
* @api {post} /api/ml/results/stopped_partitions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here a94ecaf
/** | ||
* @apiGroup ResultsService | ||
* | ||
* @api {get} /api/ml/results/:jobId/categorizer_stats |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It requires some brief description for docs to be properly generated, please check other examples
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here a94ecaf
this._job_config.analysis_config.per_partition_categorization!.enabled = enabled; | ||
} | ||
|
||
public get partitionStopOnWarn() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this name be perPartitionStopOnWarn
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here a94ecaf
@@ -81,6 +82,7 @@ export class JobCreator { | |||
} | |||
|
|||
this._datafeed_config.query = query; | |||
this._partitionField = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keeping a separate copy of the partition field name will lead to this becoming out of sync with the real partition field name in the job config, if the user edits the job's JSON.
i think we should be using the partition field from the detector which uses the mlcategory
field.
this will make the get categorizationPerPartitionField()
function trickier as it'll have to find that field each time it is called.
Perhaps a workaround would be to store the index of the categorisation detector in this class for fast look up. in the categorisation wizard that will always be 0
.
I don't see how we can deal with multiple categorisation detectors with differing partition fields in the UI. Even though it is possible to configure in the JSON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As from our conversation, I have removed this._partitionField
altogether. Since storing the index might lead to the same issue where the index being out of sync with the detectors (e.g. like deleting one of the detector), I have changed the getter function to find the first instance of detector where the keyword mlcategory exists. Since the partition_field_name
property has to be the same value in every detector that uses the keyword mlcategory, I think this change is more suitable.
import { EuiDescribedFormGroup } from '@elastic/eui'; | ||
|
||
interface Props { | ||
isOptional: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isOptional
isn't needed for this component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in a94ecaf
CategorizationJobCreator, | ||
isCategorizationJobCreator, | ||
} from '../../../../../common/job_creator'; | ||
import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i agree this would be better in a context, however newJobCapsService
is used throughout the wizards and so i'd suggest this happens in a refactoring PR later on.
jobCreator.perPartitionCategorization && | ||
jobCreator.categorizationPerPartitionField === null | ||
) { | ||
jobCreator.categorizationPerPartitionField = filteredCategories[0].id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to be safe, it would be worth adding a check for filteredCategories.length
before access it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in a94ecaf
jobIds, | ||
fieldToBucket, | ||
}); | ||
return httpService.http<any>({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like this returns a GetStoppedPartitionResult
That interface be moved to a common location and used server side and here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in a94ecaf
@@ -71,6 +76,19 @@ function getPartitionFieldsValues(legacyClient: ILegacyScopedClusterClient, payl | |||
return rs.getPartitionFieldsValues(jobId, searchTerm, criteriaFields, earliestMs, latestMs); | |||
} | |||
|
|||
function getCategorizerStats(context: RequestHandlerContext, params: any, query: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs updating to use legacyClient
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in a94ecaf
- Refactor context -> legacyclient - Update naming & formatted strings - Update schema - Update some functions to useCallback or useMemo - Rename perPartitionStopOnWarn
// looping through to find current partition_field name to prevent stale/syncing issue | ||
// possible because partition_field_name has to have same value in every detector that uses the keyword mlcategory | ||
const firstCategorizationDetector = this._detectors.find( | ||
(d) => d.by_field_name === 'mlcategory' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have a common constant for this string MLCATEGORY
in field_types
return null; | ||
} | ||
|
||
public set categorizationPerPartitionField(fieldName: string | null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should only be changing detectors which are using mlcategory
as the by_field_name
); | ||
if ( | ||
firstCategorizationDetector && | ||
'partition_field_name' in firstCategorizationDetector && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this check is needed as the undefined
below has it covered.
delete detector.partition_field_name; | ||
}); | ||
} else { | ||
if (this.categorizationPerPartitionField !== fieldName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thinking about this a bit more, this check won't catch the situation where a second mlcategory
detector has been added with a different partition field.
I think we're going to struggle to add nice UI behaviour in this situation without looping over the detectors whenever one is added to check for mlcategory
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tested this and validation fails in a bad way when we have two categorisation detectors with different partition fields.
I think we should allow this to be configured in the UI, and we should fix validation so it displays the error in the correct way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After chatting to @droberts195 we agree that the advanced job wizard shouldn't have a global way to configure the partition field for all categorisation detectors.
Instead the user should do this themselves per detector.
This means the categorizationPerPartitionField
can be moved out of the base JobCreator
class and instead live in the derived CategorizationJobCreator
class.
It also means we can put the partitionField
variable back in as there will only ever be one detector in that job.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functional tests LGTM
…r-partition # Conflicts: # docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md # src/plugins/data/server/index.ts # src/plugins/data/server/server.api.md # x-pack/plugins/reporting/server/routes/jobs.ts # x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx # x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx # x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx # x-pack/test/functional/apps/reporting_management/report_listing.ts
💔 Build Failed
Failed CI StepsBuild metrics@kbn/optimizer bundle module count
async chunks size
page load bundle size
History
To update your PR or re-run it, just comment with: |
Seems like I have accidentally pulled Dima's PR in as well with my updates yesterday. Will close this PR and create a new one. My apologies for the convenience. |
Summary
Part of meta issue #73968 which adds the ability check for per-partition in categorization job creation wizard.
UI-related changes
Advanced job creation wizard (when a category field is selected)

If viewing the results of a per-partition categorization job in the Anomaly Explorer, and if stop_on_warn was enabled, add an indicator somewhere in the view if the categorization status goes to warn for a partition. This is needed to explain to the user that there may be fewer results than there could have been.
API-related
api/ml/anomaly_detectors/{jobId}/categorizer_stats
for retrieving categorizer_stats documentsapi/ml/anomaly_detectors/{jobId}/stopped_partitions
to find out which partitions we stopped categorizing (searching for categorizer_stats documents in the job's results index)Checklist