Skip to content

Commit

Permalink
fix(slo): Optimistic updates (elastic#176548)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdelemme authored and fkanout committed Mar 4, 2024
1 parent 92d81ba commit d5cc659
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 286 deletions.
34 changes: 2 additions & 32 deletions x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n';
import { encode } from '@kbn/rison';
import type { CreateSLOInput, CreateSLOResponse, FindSLOResponse } from '@kbn/slo-schema';
import { QueryKey, useMutation, useQueryClient } from '@tanstack/react-query';
import { v4 as uuidv4 } from 'uuid';
import { paths } from '../../../common/locators/paths';
import { useKibana } from '../../utils/kibana_react';
import { sloKeys } from './query_key_factory';
Expand All @@ -37,46 +36,17 @@ export function useCreateSlo() {
return http.post<CreateSLOResponse>(`/api/observability/slos`, { body });
},
{
onMutate: async ({ slo }) => {
await queryClient.cancelQueries({ queryKey: sloKeys.lists(), exact: false });

const queriesData = queryClient.getQueriesData<FindSLOResponse>({
queryKey: sloKeys.lists(),
exact: false,
});

const [queryKey, previousData] = queriesData?.at(0) ?? [];

const newItem = { ...slo, id: uuidv4(), summary: undefined };

const optimisticUpdate = {
page: previousData?.page ?? 1,
perPage: previousData?.perPage ?? 25,
total: previousData?.total ? previousData.total + 1 : 1,
results: [...(previousData?.results ?? []), newItem],
};

if (queryKey) {
queryClient.setQueryData(queryKey, optimisticUpdate);
}

return { queryKey, previousData };
},
onSuccess: (_data, { slo }) => {
queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });

toasts.addSuccess(
i18n.translate('xpack.observability.slo.create.successNotification', {
defaultMessage: 'Successfully created {name}',
values: { name: slo.name },
})
);

queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });
},
onError: (error, { slo }, context) => {
if (context?.previousData && context?.queryKey) {
queryClient.setQueryData(context.queryKey, context.previousData);
}

toasts.addError(new Error(error.body?.message ?? error.message), {
title: i18n.translate('xpack.observability.slo.create.errorNotification', {
defaultMessage: 'Something went wrong while creating {name}',
Expand Down
37 changes: 4 additions & 33 deletions x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* 2.0.
*/

import { QueryKey, useMutation, useQueryClient } from '@tanstack/react-query';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { FindSLOResponse } from '@kbn/slo-schema';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { QueryKey, useMutation, useQueryClient } from '@tanstack/react-query';
import { useKibana } from '../../utils/kibana_react';
import { sloKeys } from './query_key_factory';

Expand Down Expand Up @@ -36,38 +36,7 @@ export function useDeleteSlo() {
}
},
{
onMutate: async (slo) => {
await queryClient.cancelQueries({ queryKey: sloKeys.lists(), exact: false });

const queriesData = queryClient.getQueriesData<FindSLOResponse>({
queryKey: sloKeys.lists(),
exact: false,
});
const [queryKey, previousData] = queriesData?.at(0) ?? [];

// taking into account partitioned slo
const matchingSloCount =
previousData?.results?.filter((result) => result.id === slo.id)?.length ?? 0;

const optimisticUpdate = {
page: previousData?.page ?? 1,
perPage: previousData?.perPage ?? 25,
total: previousData?.total ? previousData.total - matchingSloCount : 0,
results: previousData?.results?.filter((result) => result.id !== slo.id) ?? [],
};

if (queryKey) {
queryClient.setQueryData(queryKey, optimisticUpdate);
}

return { previousData, queryKey };
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (error, { name }, context) => {
if (context?.previousData && context?.queryKey) {
queryClient.setQueryData(context.queryKey, context.previousData);
}

toasts.addError(new Error(error.body?.message ?? error.message), {
title: i18n.translate('xpack.observability.slo.slo.delete.errorNotification', {
defaultMessage: 'Failed to delete {name}',
Expand All @@ -76,6 +45,8 @@ export function useDeleteSlo() {
});
},
onSuccess: (_data, { name }) => {
queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });

toasts.addSuccess(
i18n.translate('xpack.observability.slo.slo.delete.successNotification', {
defaultMessage: 'Deleted {name}',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { CreateSLOInput, SLOResponse } from '@kbn/slo-schema';
import { useQuery } from '@tanstack/react-query';
import { useKibana } from '../../utils/kibana_react';

interface SLOInspectResponse {
slo: SLOResponse;
pipeline: Record<string, any>;
rollUpTransform: TransformPutTransformRequest;
summaryTransform: TransformPutTransformRequest;
temporaryDoc: Record<string, any>;
}

export interface UseInspectSLOResponse {
data: SLOInspectResponse | undefined;
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
}

export function useFetchSloInspect(slo: CreateSLOInput, shouldInspect: boolean) {
const { http } = useKibana().services;

const { isLoading, isError, isSuccess, data } = useQuery({
queryKey: ['slo', 'inspect'],
queryFn: async ({ signal }) => {
try {
const body = JSON.stringify(slo);
const response = await http.post<SLOInspectResponse>(
'/internal/api/observability/slos/_inspect',
{
body,
signal,
}
);

return response;
} catch (error) {
// ignore error
}
},
enabled: shouldInspect,
refetchOnWindowFocus: false,
keepPreviousData: true,
});

return {
data,
isLoading,
isSuccess,
isError,
};
}
41 changes: 0 additions & 41 deletions x-pack/plugins/observability/public/hooks/slo/use_inspect_slo.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMutation } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useKibana } from '../../utils/kibana_react';
import { sloKeys } from './query_key_factory';

type ServerError = IHttpFetchError<ResponseErrorBody>;

Expand All @@ -16,6 +17,8 @@ export function useResetSlo() {
http,
notifications: { toasts },
} = useKibana().services;
const queryClient = useQueryClient();

return useMutation<string, ServerError, { id: string; name: string }>(
['resetSlo'],
({ id, name }) => {
Expand All @@ -40,6 +43,7 @@ export function useResetSlo() {
});
},
onSuccess: (_data, { name }) => {
queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });
toasts.addSuccess(
i18n.translate('xpack.observability.slo.slo.reset.successNotification', {
defaultMessage: '{name} reset successfully',
Expand Down
50 changes: 8 additions & 42 deletions x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { encode } from '@kbn/rison';
import type { FindSLOResponse, UpdateSLOInput, UpdateSLOResponse } from '@kbn/slo-schema';
import { QueryKey, useMutation, useQueryClient } from '@tanstack/react-query';
import { encode } from '@kbn/rison';
import { useKibana } from '../../utils/kibana_react';
import { paths } from '../../../common/locators/paths';
import { useKibana } from '../../utils/kibana_react';
import { sloKeys } from './query_key_factory';

type ServerError = IHttpFetchError<ResponseErrorBody>;
Expand All @@ -36,61 +36,27 @@ export function useUpdateSlo() {
return http.put<UpdateSLOResponse>(`/api/observability/slos/${sloId}`, { body });
},
{
onMutate: async ({ sloId, slo }) => {
await queryClient.cancelQueries({ queryKey: sloKeys.lists(), exact: false });

const queriesData = queryClient.getQueriesData<FindSLOResponse>({
queryKey: sloKeys.lists(),
exact: false,
});
const [queryKey, previousData] = queriesData?.at(0) ?? [];

const updatedItem = { ...slo, id: sloId };
const optimisticUpdate = {
page: previousData?.page ?? 1,
perPage: previousData?.perPage ?? 25,
total: previousData?.total ? previousData.total : 1,
results: [
...(previousData?.results?.filter((result) => result.id !== sloId) ?? []),
updatedItem,
],
};

if (queryKey) {
queryClient.setQueryData(queryKey, optimisticUpdate);
}

return { previousData, queryKey, sloId };
},
onSuccess: (_data, { slo: { name } }) => {
queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });

toasts.addSuccess(
i18n.translate('xpack.observability.slo.update.successNotification', {
defaultMessage: 'Successfully updated {name}',
values: { name },
})
);

queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });
},
onError: (error, { slo }, context) => {
if (context?.previousData && context?.queryKey) {
queryClient.setQueryData(context.queryKey, context.previousData);
}

onError: (error, { slo, sloId }, context) => {
toasts.addError(new Error(error.body?.message ?? error.message), {
title: i18n.translate('xpack.observability.slo.update.errorNotification', {
defaultMessage: 'Something went wrong when updating {name}',
values: { name: slo.name },
}),
});

if (context?.sloId) {
navigateToUrl(
http.basePath.prepend(
paths.observability.sloEditWithEncodedForm(context.sloId, encode(slo))
)
);
}
navigateToUrl(
http.basePath.prepend(paths.observability.sloEditWithEncodedForm(sloId, encode(slo)))
);
},
}
);
Expand Down
Loading

0 comments on commit d5cc659

Please sign in to comment.