mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
# Backport This will backport the following commits from `main` to `8.8`: - [Add SloGlobalDiagnosis check to SLO List and SLO Create pages (#157488)](https://github.com/elastic/kibana/pull/157488) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Coen Warmer","email":"coen.warmer@gmail.com"},"sourceCommit":{"committedDate":"2023-05-12T15:45:16Z","message":"Add SloGlobalDiagnosis check to SLO List and SLO Create pages (#157488)\n\nCo-authored-by: Kevin Delemme <kdelemme@gmail.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"e75325653c88553454270bf412ddbed700fc022b","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","v8.8.0","v8.9.0"],"number":157488,"url":"https://github.com/elastic/kibana/pull/157488","mergeCommit":{"message":"Add SloGlobalDiagnosis check to SLO List and SLO Create pages (#157488)\n\nCo-authored-by: Kevin Delemme <kdelemme@gmail.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"e75325653c88553454270bf412ddbed700fc022b"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/157488","number":157488,"mergeCommit":{"message":"Add SloGlobalDiagnosis check to SLO List and SLO Create pages (#157488)\n\nCo-authored-by: Kevin Delemme <kdelemme@gmail.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"e75325653c88553454270bf412ddbed700fc022b"}}]}] BACKPORT--> Co-authored-by: Coen Warmer <coen.warmer@gmail.com>
This commit is contained in:
parent
e5132c79d4
commit
277be81f38
7 changed files with 198 additions and 39 deletions
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export function convertErrorForUseInToast(error: Error) {
|
||||
const newError = {
|
||||
...error,
|
||||
message: Object(error).body.message,
|
||||
};
|
||||
|
||||
return newError;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { PublicLicenseJSON } from '@kbn/licensing-plugin/public';
|
||||
import type { SecurityGetUserPrivilegesResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { convertErrorForUseInToast } from './helpers/convert_error_for_use_in_toast';
|
||||
|
||||
interface SloGlobalDiagnosisResponse {
|
||||
licenseAndFeatures: PublicLicenseJSON;
|
||||
userPrivileges: SecurityGetUserPrivilegesResponse;
|
||||
sloResources: {
|
||||
[x: string]: 'OK' | 'NOT_OK';
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseFetchSloGlobalDiagnoseResponse {
|
||||
isInitialLoading: boolean;
|
||||
isLoading: boolean;
|
||||
isRefetching: boolean;
|
||||
isSuccess: boolean;
|
||||
isError: boolean;
|
||||
globalSloDiagnosis: SloGlobalDiagnosisResponse | undefined;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<SloGlobalDiagnosisResponse | undefined, unknown>>;
|
||||
}
|
||||
|
||||
export function useFetchSloGlobalDiagnosis(): UseFetchSloGlobalDiagnoseResponse {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery(
|
||||
{
|
||||
queryKey: ['fetchSloGlobalDiagnosis'],
|
||||
queryFn: async ({ signal }) => {
|
||||
try {
|
||||
const response = await http.get<SloGlobalDiagnosisResponse>(
|
||||
'/internal/observability/slos/_diagnosis',
|
||||
{
|
||||
query: {},
|
||||
signal,
|
||||
}
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw convertErrorForUseInToast(error);
|
||||
}
|
||||
},
|
||||
keepPreviousData: true,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
onError: (error: Error) => {
|
||||
toasts.addError(error, {
|
||||
title: i18n.translate('xpack.observability.slo.globalDiagnosis.errorNotification', {
|
||||
defaultMessage: 'You do not have the right permissions to use this feature.',
|
||||
}),
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
globalSloDiagnosis: data,
|
||||
isLoading,
|
||||
isInitialLoading,
|
||||
isRefetching,
|
||||
isSuccess,
|
||||
isError,
|
||||
refetch,
|
||||
};
|
||||
}
|
|
@ -15,9 +15,10 @@ import { usePluginContext } from '../../hooks/use_plugin_context';
|
|||
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
||||
import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { SloEditForm } from './components/slo_edit_form';
|
||||
import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button';
|
||||
import { useCapabilities } from '../../hooks/slo/use_capabilities';
|
||||
import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis';
|
||||
import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button';
|
||||
import { SloEditForm } from './components/slo_edit_form';
|
||||
|
||||
export function SloEditPage() {
|
||||
const {
|
||||
|
@ -25,6 +26,7 @@ export function SloEditPage() {
|
|||
http: { basePath },
|
||||
} = useKibana().services;
|
||||
const { hasWriteCapabilities } = useCapabilities();
|
||||
const { isError: hasErrorInGlobalDiagnosis } = useFetchSloGlobalDiagnosis();
|
||||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
|
||||
const { sloId } = useParams<{ sloId: string | undefined }>();
|
||||
|
@ -43,7 +45,7 @@ export function SloEditPage() {
|
|||
|
||||
const { slo, isInitialLoading } = useFetchSloDetails({ sloId });
|
||||
|
||||
if (hasRightLicense === false || !hasWriteCapabilities) {
|
||||
if (hasRightLicense === false || !hasWriteCapabilities || hasErrorInGlobalDiagnosis) {
|
||||
navigateToUrl(basePath.prepend(paths.observability.slos));
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { screen, waitFor } from '@testing-library/react';
|
|||
import { render } from '../../utils/test_helper';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list';
|
||||
import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { SlosWelcomePage } from './slos_welcome';
|
||||
import { emptySloList, sloList } from '../../data/slo/slo';
|
||||
|
@ -22,11 +23,13 @@ jest.mock('../../hooks/use_breadcrumbs');
|
|||
jest.mock('../../hooks/use_license');
|
||||
jest.mock('../../hooks/slo/use_fetch_slo_list');
|
||||
jest.mock('../../hooks/slo/use_capabilities');
|
||||
jest.mock('../../hooks/slo/use_fetch_global_diagnosis');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
const useLicenseMock = useLicense as jest.Mock;
|
||||
const useFetchSloListMock = useFetchSloList as jest.Mock;
|
||||
const useCapabilitiesMock = useCapabilities as jest.Mock;
|
||||
const useGlobalDiagnosisMock = useFetchSloGlobalDiagnosis as jest.Mock;
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
|
||||
|
@ -54,6 +57,9 @@ describe('SLOs Welcome Page', () => {
|
|||
it('renders the welcome message with subscription buttons', async () => {
|
||||
useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList });
|
||||
useLicenseMock.mockReturnValue({ hasAtLeast: () => false });
|
||||
useGlobalDiagnosisMock.mockReturnValue({
|
||||
isError: false,
|
||||
});
|
||||
|
||||
render(<SlosWelcomePage />);
|
||||
|
||||
|
@ -66,6 +72,9 @@ describe('SLOs Welcome Page', () => {
|
|||
describe('when the correct license is found', () => {
|
||||
beforeEach(() => {
|
||||
useLicenseMock.mockReturnValue({ hasAtLeast: () => true });
|
||||
useGlobalDiagnosisMock.mockReturnValue({
|
||||
isError: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when loading is done and no results are found', () => {
|
||||
|
@ -79,6 +88,24 @@ describe('SLOs Welcome Page', () => {
|
|||
hasReadCapabilities: true,
|
||||
});
|
||||
|
||||
render(<SlosWelcomePage />);
|
||||
|
||||
expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
|
||||
|
||||
const createNewSloButton = screen.queryByTestId('o11ySloListWelcomePromptCreateSloButton');
|
||||
|
||||
expect(createNewSloButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables the create slo button when no cluster permissions capabilities', async () => {
|
||||
useCapabilitiesMock.mockReturnValue({
|
||||
hasWriteCapabilities: true,
|
||||
hasReadCapabilities: true,
|
||||
});
|
||||
useGlobalDiagnosisMock.mockReturnValue({
|
||||
isError: true,
|
||||
});
|
||||
|
||||
render(<SlosWelcomePage />);
|
||||
expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
|
||||
|
||||
|
@ -87,6 +114,10 @@ describe('SLOs Welcome Page', () => {
|
|||
});
|
||||
|
||||
it('should display the welcome message with a Create new SLO button which should navigate to the SLO Creation page', async () => {
|
||||
useGlobalDiagnosisMock.mockReturnValue({
|
||||
isError: false,
|
||||
});
|
||||
|
||||
render(<SlosWelcomePage />);
|
||||
expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
|
||||
|
||||
|
@ -103,6 +134,9 @@ describe('SLOs Welcome Page', () => {
|
|||
describe('when loading is done and results are found', () => {
|
||||
beforeEach(() => {
|
||||
useFetchSloListMock.mockReturnValue({ isLoading: false, sloList });
|
||||
useGlobalDiagnosisMock.mockReturnValue({
|
||||
isError: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to the SLO List page', async () => {
|
||||
|
|
|
@ -21,10 +21,11 @@ import { i18n } from '@kbn/i18n';
|
|||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useCapabilities } from '../../hooks/slo/use_capabilities';
|
||||
import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list';
|
||||
import { paths } from '../../config/paths';
|
||||
import illustration from './assets/illustration.svg';
|
||||
import { useCapabilities } from '../../hooks/slo/use_capabilities';
|
||||
import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis';
|
||||
|
||||
export function SlosWelcomePage() {
|
||||
const {
|
||||
|
@ -32,6 +33,7 @@ export function SlosWelcomePage() {
|
|||
http: { basePath },
|
||||
} = useKibana().services;
|
||||
const { hasWriteCapabilities } = useCapabilities();
|
||||
const { isError: hasErrorInGlobalDiagnosis } = useFetchSloGlobalDiagnosis();
|
||||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
|
||||
const { hasAtLeast } = useLicense();
|
||||
|
@ -44,7 +46,8 @@ export function SlosWelcomePage() {
|
|||
navigateToUrl(basePath.prepend(paths.observability.sloCreate));
|
||||
};
|
||||
|
||||
const hasSlosAndHasPermissions = total > 0 && hasAtLeast('platinum') === true;
|
||||
const hasSlosAndHasPermissions =
|
||||
total > 0 && hasAtLeast('platinum') === true && !hasErrorInGlobalDiagnosis;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSlosAndHasPermissions) {
|
||||
|
@ -110,7 +113,7 @@ export function SlosWelcomePage() {
|
|||
fill
|
||||
color="primary"
|
||||
onClick={handleClickCreateSlo}
|
||||
disabled={!hasWriteCapabilities}
|
||||
disabled={!hasWriteCapabilities || hasErrorInGlobalDiagnosis}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slo.sloList.welcomePrompt.buttonLabel', {
|
||||
defaultMessage: 'Create SLO',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { badRequest } from '@hapi/boom';
|
||||
import { badRequest, forbidden, failedDependency } from '@hapi/boom';
|
||||
import {
|
||||
createSLOParamsSchema,
|
||||
deleteSLOParamsSchema,
|
||||
|
@ -275,7 +275,15 @@ const getDiagnosisRoute = createObservabilityServerRoute({
|
|||
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const licensing = await context.licensing;
|
||||
|
||||
return getGlobalDiagnosis(esClient, licensing);
|
||||
try {
|
||||
const response = await getGlobalDiagnosis(esClient, licensing);
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error.cause.statusCode === 403) {
|
||||
throw forbidden('Insufficient Elasticsearch cluster permissions to access feature.');
|
||||
}
|
||||
throw failedDependency(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -26,15 +26,19 @@ export async function getGlobalDiagnosis(
|
|||
esClient: ElasticsearchClient,
|
||||
licensing: LicensingApiRequestHandlerContext
|
||||
) {
|
||||
const licenseInfo = licensing.license.toJSON();
|
||||
const userPrivileges = await esClient.security.getUserPrivileges();
|
||||
const sloResources = await getSloResourcesDiagnosis(esClient);
|
||||
try {
|
||||
const licenseInfo = licensing.license.toJSON();
|
||||
const userPrivileges = await esClient.security.getUserPrivileges();
|
||||
const sloResources = await getSloResourcesDiagnosis(esClient);
|
||||
|
||||
return {
|
||||
licenseAndFeatures: licenseInfo,
|
||||
userPrivileges,
|
||||
sloResources,
|
||||
};
|
||||
return {
|
||||
licenseAndFeatures: licenseInfo,
|
||||
userPrivileges,
|
||||
sloResources,
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSloDiagnosis(
|
||||
|
@ -79,29 +83,36 @@ export async function getSloDiagnosis(
|
|||
}
|
||||
|
||||
async function getSloResourcesDiagnosis(esClient: ElasticsearchClient) {
|
||||
const indexTemplateExists = await esClient.indices.existsIndexTemplate({
|
||||
name: SLO_INDEX_TEMPLATE_NAME,
|
||||
});
|
||||
|
||||
const mappingsTemplateExists = await esClient.cluster.existsComponentTemplate({
|
||||
name: SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME,
|
||||
});
|
||||
|
||||
const settingsTemplateExists = await esClient.cluster.existsComponentTemplate({
|
||||
name: SLO_COMPONENT_TEMPLATE_SETTINGS_NAME,
|
||||
});
|
||||
|
||||
let ingestPipelineExists = true;
|
||||
try {
|
||||
await esClient.ingest.getPipeline({ id: SLO_INGEST_PIPELINE_NAME });
|
||||
} catch (err) {
|
||||
ingestPipelineExists = false;
|
||||
}
|
||||
const indexTemplateExists = await esClient.indices.existsIndexTemplate({
|
||||
name: SLO_INDEX_TEMPLATE_NAME,
|
||||
});
|
||||
|
||||
return {
|
||||
[SLO_INDEX_TEMPLATE_NAME]: indexTemplateExists ? OK : NOT_OK,
|
||||
[SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME]: mappingsTemplateExists ? OK : NOT_OK,
|
||||
[SLO_COMPONENT_TEMPLATE_SETTINGS_NAME]: settingsTemplateExists ? OK : NOT_OK,
|
||||
[SLO_INGEST_PIPELINE_NAME]: ingestPipelineExists ? OK : NOT_OK,
|
||||
};
|
||||
const mappingsTemplateExists = await esClient.cluster.existsComponentTemplate({
|
||||
name: SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME,
|
||||
});
|
||||
|
||||
const settingsTemplateExists = await esClient.cluster.existsComponentTemplate({
|
||||
name: SLO_COMPONENT_TEMPLATE_SETTINGS_NAME,
|
||||
});
|
||||
|
||||
let ingestPipelineExists = true;
|
||||
try {
|
||||
await esClient.ingest.getPipeline({ id: SLO_INGEST_PIPELINE_NAME });
|
||||
} catch (err) {
|
||||
ingestPipelineExists = false;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return {
|
||||
[SLO_INDEX_TEMPLATE_NAME]: indexTemplateExists ? OK : NOT_OK,
|
||||
[SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME]: mappingsTemplateExists ? OK : NOT_OK,
|
||||
[SLO_COMPONENT_TEMPLATE_SETTINGS_NAME]: settingsTemplateExists ? OK : NOT_OK,
|
||||
[SLO_INGEST_PIPELINE_NAME]: ingestPipelineExists ? OK : NOT_OK,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.meta.statusCode === 403) {
|
||||
throw new Error('Insufficient permissions to access Elasticsearch Cluster', { cause: err });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue