Skip to content
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

fix(slo): Optimistic updates #176548

Merged
merged 8 commits into from
Feb 8, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Remove usage of useMutation for non mutation API hook
kdelemme committed Feb 8, 2024

Verified

This commit was signed with the committer’s verified signature.
commit 2298f7c5655cbe96cd250424c3afebb2c803093d
57 changes: 38 additions & 19 deletions x-pack/plugins/observability/public/hooks/slo/use_inspect_slo.ts
Original file line number Diff line number Diff line change
@@ -5,15 +5,11 @@
* 2.0.
*/

import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import type { FindSLOResponse, SLOResponse } from '@kbn/slo-schema';
import { QueryKey, useMutation } from '@tanstack/react-query';
import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { CreateSLOInput } from '@kbn/slo-schema';
import type { CreateSLOInput, SLOResponse } from '@kbn/slo-schema';
import { useQuery } from '@tanstack/react-query';
import { useKibana } from '../../utils/kibana_react';

type ServerError = IHttpFetchError<ResponseErrorBody>;

interface SLOInspectResponse {
slo: SLOResponse;
pipeline: Record<string, any>;
@@ -22,20 +18,43 @@ interface SLOInspectResponse {
temporaryDoc: Record<string, any>;
}

export function useInspectSlo() {
export interface UseInspectSLOResponse {
data: SLOInspectResponse | undefined;
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
}

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

return useMutation<
SLOInspectResponse,
ServerError,
{ slo: CreateSLOInput },
{ previousData?: FindSLOResponse; queryKey?: QueryKey }
>(
['inspectSlo'],
({ slo }) => {
const body = JSON.stringify(slo);
return http.post<SLOInspectResponse>(`/internal/api/observability/slos/_inspect`, { body });
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,
};
}
Original file line number Diff line number Diff line change
@@ -5,22 +5,20 @@
* 2.0.
*/

import { GetSLOResponse } from '@kbn/slo-schema';
import React from 'react';
import { InPortal } from 'react-reverse-portal';
import { GetSLOResponse } from '@kbn/slo-schema';
import { CreateSLOForm } from '../../types';
import { SLOInspectWrapper } from './slo_inspect';
import { InspectSLOPortalNode } from '../../slo_edit';
import { SLOInspectWrapper } from './slo_inspect';

export interface SloInspectPortalProps {
getValues: () => CreateSLOForm;
trigger: () => Promise<boolean>;
export interface Props {
slo?: GetSLOResponse;
}
export function InspectSLOPortal(props: SloInspectPortalProps) {

export function InspectSLOPortal({ slo }: Props) {
return (
<InPortal node={InspectSLOPortalNode}>
<SLOInspectWrapper {...props} />
<SLOInspectWrapper slo={slo} />
</InPortal>
);
}
Original file line number Diff line number Diff line change
@@ -6,115 +6,115 @@
*/
import { i18n } from '@kbn/i18n';

import React, { ReactNode, useState } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useFetcher } from '@kbn/observability-shared-plugin/public';
import {
EuiFlyout,
EuiAccordion,
EuiButton,
EuiButtonIcon,
EuiCodeBlock,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutFooter,
EuiSpacer,
EuiFlyoutBody,
EuiToolTip,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiLoadingSpinner,
EuiAccordion,
EuiButtonIcon,
EuiSpacer,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import {
IngestPipelinesListParams,
INGEST_PIPELINES_APP_LOCATOR,
INGEST_PIPELINES_PAGES,
IngestPipelinesListParams,
} from '@kbn/ingest-pipelines-plugin/public';
import { SloInspectPortalProps } from './inspect_slo_portal';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useFetcher } from '@kbn/observability-shared-plugin/public';
import React, { ReactNode, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { ObservabilityPublicPluginsStart } from '../../../..';
import { useInspectSlo } from '../../../../hooks/slo/use_inspect_slo';
import { transformCreateSLOFormToCreateSLOInput } from '../../helpers/process_slo_form_values';
import { enableInspectEsQueries } from '../../../../../common';
import { useInspectSlo } from '../../../../hooks/slo/use_inspect_slo';
import { usePluginContext } from '../../../../hooks/use_plugin_context';
import { transformCreateSLOFormToCreateSLOInput } from '../../helpers/process_slo_form_values';
import { CreateSLOForm } from '../../types';
import { Props } from './inspect_slo_portal';

export function SLOInspectWrapper(props: SloInspectPortalProps) {
export function SLOInspectWrapper({ slo }: Props) {
const {
services: { uiSettings },
} = useKibana();

const { isDev } = usePluginContext();

const isInspectorEnabled = uiSettings?.get<boolean>(enableInspectEsQueries);

return isDev || isInspectorEnabled ? <SLOInspect {...props} /> : null;
return isDev || isInspectorEnabled ? <SLOInspect slo={slo} /> : null;
}

function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) {
function SLOInspect({ slo }: Props) {
const { share, http } = useKibana<ObservabilityPublicPluginsStart>().services;
const { trigger, getValues } = useFormContext<CreateSLOForm>();

const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
const { mutateAsync: inspectSlo, data, isLoading } = useInspectSlo();
const [isFormValid, setFormValid] = useState(false);

const { data: sloData } = useFetcher(async () => {
if (!isFlyoutVisible) {
return;
}
const isValid = await trigger();
if (!isValid) {
return;
}
const sloForm = transformCreateSLOFormToCreateSLOInput(getValues());
inspectSlo({ slo: { ...sloForm, id: slo?.id, revision: slo?.revision } });
return sloForm;
}, [isFlyoutVisible, trigger, getValues, inspectSlo, slo]);
const sloFormValues = transformCreateSLOFormToCreateSLOInput(getValues());
const { data: inspectSloData, isLoading } = useInspectSlo(
{ ...sloFormValues, id: slo?.id, revision: slo?.revision },
isFlyoutVisible && isFormValid
);

const { data: pipeLineUrl } = useFetcher(async () => {
const ingestPipeLocator = share.url.locators.get<IngestPipelinesListParams>(
INGEST_PIPELINES_APP_LOCATOR
);
const ingestPipeLineId = data?.pipeline?.id;
const ingestPipeLineId = inspectSloData?.pipeline?.id;
return ingestPipeLocator?.getUrl({
pipelineId: ingestPipeLineId,
page: INGEST_PIPELINES_PAGES.LIST,
});
}, [data?.pipeline?.id, share.url.locators]);
}, [inspectSloData?.pipeline?.id, share.url.locators]);

const closeFlyout = () => {
setIsFlyoutVisible(false);
setIsInspecting(false);
setFormValid(false);
};

const [isInspecting, setIsInspecting] = useState(false);
const onButtonClick = () => {
trigger().then((isValid) => {
if (isValid) {
setIsInspecting(() => !isInspecting);
setIsFlyoutVisible(() => !isFlyoutVisible);
}
});
const handleInspectButtonClick = async () => {
const isFormValid = await trigger();
if (!isFormValid) {
setFormValid(false);
return;
}

setFormValid(true);
setIsFlyoutVisible(true);
};

let flyout;

if (isFlyoutVisible) {
flyout = (
<EuiFlyout ownFocus onClose={closeFlyout} aria-labelledby="flyoutTitle">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id="flyoutTitle">{CONFIG_LABEL}</h2>
<h2 id="flyoutTitle">
{i18n.translate('xpack.observability.monitorInspect.configLabel', {
defaultMessage: 'SLO Configurations',
})}
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{isLoading && <LoadingState />}
<EuiSpacer size="m" />
{data && (
{inspectSloData && (
<>
<CodeBlockAccordion
id="slo"
label={i18n.translate(
'xpack.observability.sLOInspect.codeBlockAccordion.sloConfigurationLabel',
{ defaultMessage: 'SLO configuration' }
)}
json={data.slo}
json={inspectSloData.slo}
/>
<EuiSpacer size="s" />
<CodeBlockAccordion
@@ -123,7 +123,7 @@ function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) {
'xpack.observability.sLOInspect.codeBlockAccordion.rollupTransformLabel',
{ defaultMessage: 'Rollup transform' }
)}
json={data.rollUpTransform}
json={inspectSloData.rollUpTransform}
extraAction={
<EuiButtonIcon
iconType="link"
@@ -140,7 +140,7 @@ function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) {
'xpack.observability.sLOInspect.codeBlockAccordion.summaryTransformLabel',
{ defaultMessage: 'Summary transform' }
)}
json={data.summaryTransform}
json={inspectSloData.summaryTransform}
extraAction={
<EuiButtonIcon
iconType="link"
@@ -164,7 +164,7 @@ function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) {
href={pipeLineUrl}
/>
}
json={data.pipeline}
json={inspectSloData.pipeline}
/>
<EuiSpacer size="s" />

@@ -174,7 +174,7 @@ function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) {
'xpack.observability.sLOInspect.codeBlockAccordion.temporaryDocumentLabel',
{ defaultMessage: 'Temporary document' }
)}
json={data.temporaryDoc}
json={inspectSloData.temporaryDoc}
/>
</>
)}
@@ -193,19 +193,30 @@ function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) {
</EuiFlyout>
);
}

return (
<>
<EuiToolTip
content={sloData ? VIEW_FORMATTED_CONFIG_LABEL : VALID_CONFIG_LABEL}
content={
isFormValid
? i18n.translate('xpack.observability.slo.viewFormattedResourcesConfigsButtonLabel', {
defaultMessage: 'View formatted resources configs for SLO',
})
: i18n.translate('xpack.observability.slo.formattedConfigLabel.valid', {
defaultMessage: 'Only valid form configurations can be inspected.',
})
}
repositionOnScroll
>
<EuiButton
data-test-subj="syntheticsMonitorInspectShowFlyoutExampleButton"
onClick={onButtonClick}
onClick={handleInspectButtonClick}
iconType="inspect"
iconSide="left"
>
{SLO_INSPECT_LABEL}
{i18n.translate('xpack.observability.sLOInspect.sLOInspectButtonLabel', {
defaultMessage: 'SLO Inspect',
})}
</EuiButton>
</EuiToolTip>

@@ -251,20 +262,3 @@ export function LoadingState() {
</EuiFlexGroup>
);
}

const SLO_INSPECT_LABEL = i18n.translate('xpack.observability.sLOInspect.sLOInspectButtonLabel', {
defaultMessage: 'SLO Inspect',
});

const VIEW_FORMATTED_CONFIG_LABEL = i18n.translate(
'xpack.observability.slo.viewFormattedResourcesConfigsButtonLabel',
{ defaultMessage: 'View formatted resources configs for SLO' }
);

const VALID_CONFIG_LABEL = i18n.translate('xpack.observability.slo.formattedConfigLabel.valid', {
defaultMessage: 'Only valid form configurations can be inspected.',
});

const CONFIG_LABEL = i18n.translate('xpack.observability.monitorInspect.configLabel', {
defaultMessage: 'SLO Configurations',
});
Original file line number Diff line number Diff line change
@@ -170,7 +170,7 @@ export function SloEditForm({ slo }: Props) {
/>
</EuiFlexGroup>
</EuiFlexGroup>
<InspectSLOPortal trigger={trigger} getValues={getValues} slo={slo} />
<InspectSLOPortal slo={slo} />
</FormProvider>
</>
);