Skip to content

Commit

Permalink
[ML] Accessibility: Make links on job validation step distinguished f…
Browse files Browse the repository at this point in the history
…rom surrounding text (#160608)

## Summary

Resolves #160379

Adds heading to the validation messages to make all links rendered as
part of the text content.

<img width="1224" alt="image"
src="https://github.com/elastic/kibana/assets/5236598/47416d44-7b3a-4694-88e2-43605bacc04f">


### Checklist

- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
  • Loading branch information
darnautov authored Jun 27, 2023
1 parent b33d008 commit 59bd9f6
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 8 deletions.
6 changes: 5 additions & 1 deletion x-pack/plugins/ml/common/constants/messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('Constants: Messages parseMessages()', () => {
{
id: 'job_id_invalid',
status: 'error',
heading: 'Job ID',
text: 'Job ID is invalid. It can contain lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores and must start and end with an alphanumeric character.',
url: 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms',
},
Expand Down Expand Up @@ -114,6 +115,7 @@ describe('Constants: Messages parseMessages()', () => {
fieldName: 'order_id',
id: 'cardinality_partition_field',
status: 'warning',
heading: 'Partition field cardinality',
text: 'Cardinality of partition_field "order_id" is above 1000 and might result in high memory usage.',
url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-run-jobs.html#ml-ad-cardinality',
},
Expand All @@ -135,13 +137,15 @@ describe('Constants: Messages parseMessages()', () => {
{
id: 'success_influencers',
status: 'success',
text: 'Influencer configuration passed the validation checks.',
heading: 'Influencer configuration',
text: 'Passed the validation checks.',
url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-run-jobs.html#ml-ad-influencers',
},
{
id: 'half_estimated_mml_greater_than_mml',
mml: '1MB',
status: 'warning',
heading: 'Model memory limit',
text: 'The specified model memory limit is less than half of the estimated model memory limit and will likely hit the hard limit.',
url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-run-jobs.html#ml-ad-model-memory-limits',
},
Expand Down
141 changes: 135 additions & 6 deletions x-pack/plugins/ml/common/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
return {
categorizer_detector_missing_per_partition_field: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizerMissingPerPartitionFieldHeading',
{
defaultMessage: 'Per-partition categorization',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizerMissingPerPartitionFieldMessage',
{
Expand All @@ -62,11 +68,17 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
categorizer_varying_per_partition_fields: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizerVaryingPerPartitionFieldNamesHeading',
{
defaultMessage: 'Per-partition categorization',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizerVaryingPerPartitionFieldNamesMessage',
{
defaultMessage:
'Detectors with keyword "mlcategory" cannot have different partition_field_name when per-partition categorization is enabled. Found [{fields}].',
'Detectors with keyword "mlcategory " cannot have different partition_field_name when per-partition categorization is enabled. Found [{fields}].',

values: {
fields: '"{{fields}}"',
Expand All @@ -77,8 +89,14 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
field_not_aggregatable: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.fieldNotAggregatableHeading',
{
defaultMessage: 'Detector field',
}
),
text: i18n.translate('xpack.ml.models.jobValidation.messages.fieldNotAggregatableMessage', {
defaultMessage: 'Detector field {fieldName} is not an aggregatable field.',
defaultMessage: '{fieldName} is not an aggregatable field.',
values: {
fieldName: '"{{fieldName}}"',
},
Expand All @@ -87,6 +105,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
fields_not_aggregatable: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.fieldsNotAggregatableHeading',
{
defaultMessage: 'Detector fields',
}
),
text: i18n.translate('xpack.ml.models.jobValidation.messages.fieldsNotAggregatableMessage', {
defaultMessage: 'One of the detector fields is not an aggregatable field.',
}),
Expand Down Expand Up @@ -125,6 +149,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
cardinality_by_field: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.cardinalityByFieldHeading', {
defaultMessage: 'Field cardinality',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.cardinalityByFieldMessage', {
defaultMessage:
'Cardinality of {fieldName} is above 1000 and might result in high memory usage.',
Expand All @@ -136,6 +163,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
cardinality_over_field_low: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.cardinalityOverFieldLowHeading',
{
defaultMessage: 'Over field cardinality',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.cardinalityOverFieldLowMessage',
{
Expand All @@ -150,6 +183,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
cardinality_over_field_high: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.cardinalityOverFieldHighHeading',
{
defaultMessage: 'Over field cardinality',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.cardinalityOverFieldHighMessage',
{
Expand All @@ -164,6 +203,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
cardinality_partition_field: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.cardinalityPartitionFieldHeading',
{
defaultMessage: 'Partition field cardinality',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.cardinalityPartitionFieldMessage',
{
Expand Down Expand Up @@ -192,21 +237,33 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
categorization_filters_valid: {
status: VALIDATION_STATUS.SUCCESS,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizationFiltersValidHeading',
{
defaultMessage: 'Categorization filters',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizationFiltersValidMessage',
{
defaultMessage: 'Categorization filters checks passed.',
defaultMessage: 'All checks passed.',
}
),
url: docLinks?.links.ml.anomalyDetectionConfiguringCategories,
},
categorization_filters_invalid: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizationFiltersInvalidHeading',
{
defaultMessage: 'Categorization filters',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.categorizationFiltersInvalidMessage',
{
defaultMessage:
'The categorization filters configuration is invalid. ' +
'Configuration is invalid. ' +
'Make sure filters are valid regular expressions and {categorizationFieldName} is set.',
values: {
categorizationFieldName: '"categorization_field_name"',
Expand All @@ -217,6 +274,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
bucket_span_empty: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.bucketSpanEmptyHeading', {
defaultMessage: 'Bucket span',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.bucketSpanEmptyMessage', {
defaultMessage: 'The bucket span field must be specified.',
}),
Expand Down Expand Up @@ -280,6 +340,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
detectors_duplicates: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsDuplicatesHeading', {
defaultMessage: 'Detector duplicates',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsDuplicatesMessage', {
defaultMessage:
'Duplicate detectors were found. Detectors having the same combined configuration for ' +
Expand All @@ -297,13 +360,22 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
detectors_empty: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsEmptyHeading', {
defaultMessage: 'Detectors',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsEmptyMessage', {
defaultMessage: 'No detectors were found. At least one detector must be specified.',
}),
url: docLinks?.links.ml.anomalyDetectionDetectors,
},
detectors_function_empty: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.detectorsFunctionEmptyHeading',
{
defaultMessage: 'Detector functions',
}
),
text: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsFunctionEmptyMessage', {
defaultMessage: 'One of the detector functions is empty.',
}),
Expand Down Expand Up @@ -339,6 +411,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
influencer_high: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.influencerHighHeading', {
defaultMessage: 'Influencers',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.influencerHighMessage', {
defaultMessage:
'The job configuration includes more than 3 influencers. ' +
Expand All @@ -348,6 +423,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
influencer_low: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.influencerLowHeading', {
defaultMessage: 'Influencers',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.influencerLowMessage', {
defaultMessage:
'No influencers have been configured. Picking an influencer is strongly recommended.',
Expand All @@ -356,6 +434,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
influencer_low_suggestion: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.influencerLowSuggestionHeading',
{
defaultMessage: 'Influencers',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.influencerLowSuggestionMessage',
{
Expand All @@ -368,6 +452,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
influencer_low_suggestions: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.influencerLowSuggestionsHeading',
{
defaultMessage: 'Influencers',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.influencerLowSuggestionsMessage',
{
Expand All @@ -380,13 +470,19 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
job_id_empty: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.jobIdEmptyHeading', {
defaultMessage: 'Job ID',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.jobIdEmptyMessage', {
defaultMessage: 'Job ID field must not be empty.',
defaultMessage: 'Field must not be empty.',
}),
url: docLinks?.links.ml.anomalyDetectionJobResource,
},
job_id_invalid: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.jobIdInvalidHeading', {
defaultMessage: 'Job ID',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.jobIdInvalidMessage', {
defaultMessage:
'Job ID is invalid. It can contain lowercase alphanumeric (a-z and 0-9) characters, ' +
Expand All @@ -396,6 +492,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
job_id_invalid_max_length: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.jobIdInvalidMaxLengthErrorHeading',
{
defaultMessage: 'Job ID',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.jobIdInvalidMaxLengthErrorMessage',
{
Expand Down Expand Up @@ -426,6 +528,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
job_group_id_invalid: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.jobGroupIdInvalidHeading', {
defaultMessage: 'Group name',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.jobGroupIdInvalidMessage', {
defaultMessage:
'One of the job group names is invalid. They can contain lowercase ' +
Expand All @@ -435,6 +540,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
job_group_id_invalid_max_length: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.jobGroupIdInvalidMaxLengthErrorHeading',
{
defaultMessage: 'Group name',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.jobGroupIdInvalidMaxLengthErrorMessage',
{
Expand Down Expand Up @@ -503,8 +614,11 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
success_influencers: {
status: VALIDATION_STATUS.SUCCESS,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.successInfluencersHeading', {
defaultMessage: 'Influencer configuration',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.successInfluencersMessage', {
defaultMessage: 'Influencer configuration passed the validation checks.',
defaultMessage: 'Passed the validation checks.',
}),
url: docLinks?.links.ml.anomalyDetectionInfluencers,
},
Expand Down Expand Up @@ -538,6 +652,9 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
mml_value_invalid: {
status: VALIDATION_STATUS.ERROR,
heading: i18n.translate('xpack.ml.models.jobValidation.messages.mmlValueInvalidHeading', {
defaultMessage: 'Model memory limit',
}),
text: i18n.translate('xpack.ml.models.jobValidation.messages.mmlValueInvalidMessage', {
defaultMessage:
'{mml} is not a valid value for model memory limit. The value needs to be at least ' +
Expand All @@ -548,6 +665,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
half_estimated_mml_greater_than_mml: {
status: VALIDATION_STATUS.WARNING,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.halfEstimatedMmlGreaterThanMmlHeading',
{
defaultMessage: 'Model memory limit',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.halfEstimatedMmlGreaterThanMmlMessage',
{
Expand All @@ -560,6 +683,12 @@ export const getMessages = once((docLinks?: DocLinksStart) => {
},
estimated_mml_greater_than_mml: {
status: VALIDATION_STATUS.INFO,
heading: i18n.translate(
'xpack.ml.models.jobValidation.messages.halfEstimatedMmlGreaterThanMmlHeading',
{
defaultMessage: 'Model memory limit',
}
),
text: i18n.translate(
'xpack.ml.models.jobValidation.messages.estimatedMmlGreaterThanMmlMessage',
{
Expand Down
2 changes: 1 addition & 1 deletion x-pack/test/accessibility/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./apps/index_lifecycle_management'),
// https://github.com/elastic/kibana/issues/153596
// https://github.com/elastic/kibana/issues/153592
// require.resolve('./apps/ml'),
require.resolve('./apps/ml'),
// require.resolve('./apps/transform'),
require.resolve('./apps/lens'),
require.resolve('./apps/upgrade_assistant'),
Expand Down

0 comments on commit 59bd9f6

Please sign in to comment.