[ML] Single Metric Viewer embeddable: show error message when insufficient permissions (#180858)

## Summary

When the user does not have enough permissions, ensures that the single
metric viewer panel in dashboards shows an error message (similar to the
other ML dashboard panels) instead of just being blank.

Before:

![Screenshot 2024-04-15 at 09 45
44](b0c6d180-4a0b-42dd-89e3-52206759d98e)


After:

<img width="1201" alt="image"
src="3a38dab9-da9a-48d0-8479-515f28b92329">



### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] 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))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
Melissa Alvarez 2024-04-16 14:39:50 -06:00 committed by GitHub
parent b900b86c1b
commit fa90d2fcdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 10 deletions

View file

@ -183,7 +183,7 @@ describe('EmbeddableAnomalyChartsContainer', () => {
/>,
defaultOptions
);
const errorMessage = await findByText('Unable to load the ML anomaly explorer data');
const errorMessage = await findByText('Unable to load the data for the anomaly charts');
expect(errorMessage).toBeDefined();
});
});

View file

@ -151,7 +151,7 @@ export const EmbeddableAnomalyChartsContainer: FC<EmbeddableAnomalyChartsContain
title={
<FormattedMessage
id="xpack.ml.anomalyChartsEmbeddable.errorMessage"
defaultMessage="Unable to load the ML anomaly explorer data"
defaultMessage="Unable to load the data for the anomaly charts"
/>
}
color="danger"

View file

@ -259,7 +259,7 @@ export const getAnomalySwimLaneEmbeddableFactory = (
title={
<FormattedMessage
id="xpack.ml.swimlaneEmbeddable.errorMessage"
defaultMessage="Unable to load the ML swim lane data"
defaultMessage="Unable to load the data for the swim lane"
/>
}
color="danger"

View file

@ -7,12 +7,15 @@
import type { FC } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import moment from 'moment';
import { EuiResizeObserver } from '@elastic/eui';
import type { Observable } from 'rxjs';
import { throttle } from 'lodash';
import type { MlJob } from '@elastic/elasticsearch/lib/api/types';
import usePrevious from 'react-use/lib/usePrevious';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import { useToastNotificationService } from '../../application/services/toast_notification_service';
import { useEmbeddableExecutionContext } from '../common/use_embeddable_execution_context';
import { useSingleMetricViewerInputResolver } from './use_single_metric_viewer_input_resolver';
@ -63,6 +66,7 @@ export const EmbeddableSingleMetricViewerContainer: FC<
const [selectedJob, setSelectedJob] = useState<MlJob | undefined>();
const [autoZoomDuration, setAutoZoomDuration] = useState<number | undefined>();
const [jobsLoaded, setJobsLoaded] = useState(false);
const [error, setError] = useState<string | undefined>();
const { mlApiServices, mlJobService } = services[2];
const { data, bounds, lastRefresh } = useSingleMetricViewerInputResolver(
@ -85,8 +89,13 @@ export const EmbeddableSingleMetricViewerContainer: FC<
useEffect(function setUpJobsLoaded() {
async function loadJobs() {
await mlJobService.loadJobsWrapper();
setJobsLoaded(true);
try {
await mlJobService.loadJobsWrapper();
setJobsLoaded(true);
} catch (e) {
const errorMessage = extractErrorMessage(e);
setError(errorMessage);
}
}
loadJobs();
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -95,15 +104,20 @@ export const EmbeddableSingleMetricViewerContainer: FC<
useEffect(
function setUpSelectedJob() {
async function fetchSelectedJob() {
if (mlApiServices && selectedJobId !== undefined) {
const { jobs } = await mlApiServices.getJobs({ jobId: selectedJobId });
const job = jobs[0];
setSelectedJob(job);
if (mlApiServices && selectedJobId !== undefined && error === undefined) {
try {
const { jobs } = await mlApiServices.getJobs({ jobId: selectedJobId });
const job = jobs[0];
setSelectedJob(job);
} catch (e) {
const errorMessage = extractErrorMessage(e);
setError(errorMessage);
}
}
}
fetchSelectedJob();
},
[selectedJobId, mlApiServices]
[selectedJobId, mlApiServices, error]
);
useEffect(
@ -159,6 +173,24 @@ export const EmbeddableSingleMetricViewerContainer: FC<
const containerPadding = 10;
if (error) {
return (
<EuiCallOut
title={
<FormattedMessage
id="xpack.ml.singleMetricViewerEmbeddable.errorMessage"
defaultMessage="Unable to load the data for the single metric viewer"
/>
}
color="danger"
iconType="warning"
css={{ width: '100%' }}
>
<p>{error}</p>
</EuiCallOut>
);
}
return (
<EuiResizeObserver onResize={resizeHandler}>
{(resizeRef) => (