Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4273279
try async
shahzad31 Sep 13, 2024
0ef1af1
update jest tests
shahzad31 Sep 13, 2024
60c97c9
Merge branch 'main' into slo-create-perf
shahzad31 Sep 13, 2024
c19ed76
Merge branch 'main' of https://github.com/elastic/kibana into slo-cre…
shahzad31 Sep 13, 2024
bb84431
retry test
shahzad31 Sep 13, 2024
eacead6
Merge branch 'slo-create-perf' of https://github.com/shahzad31/kibana…
shahzad31 Sep 13, 2024
4896d88
Merge branch 'main' into slo-create-perf
shahzad31 Sep 16, 2024
26899ba
Merge branch 'main' of https://github.com/elastic/kibana into slo-cre…
shahzad31 Sep 24, 2024
8cafcab
PR feedback
shahzad31 Sep 24, 2024
a7e18e4
fix tests
shahzad31 Sep 24, 2024
59c211c
Merge branch 'main' of https://github.com/elastic/kibana into slo-cre…
shahzad31 Sep 26, 2024
1e5fda8
more PR feedback
shahzad31 Sep 26, 2024
62c20ce
update api tests
shahzad31 Sep 26, 2024
6edd717
Merge branch 'main' of https://github.com/elastic/kibana into slo-cre…
shahzad31 Sep 26, 2024
6e83e3b
react hook pr feedback
shahzad31 Sep 26, 2024
6219a5a
final version feedback
shahzad31 Sep 26, 2024
7ff11dc
Merge branch 'main' of https://github.com/elastic/kibana into slo-cre…
shahzad31 Sep 27, 2024
8b0f5da
update jest
shahzad31 Sep 27, 2024
9e0da5e
Merge branch 'main' of https://github.com/elastic/kibana into slo-cre…
shahzad31 Sep 27, 2024
6257fb2
update types
shahzad31 Sep 27, 2024
0d88c2f
Merge branch 'main' into slo-create-perf
shahzad31 Sep 30, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import React from 'react';
import { useMutation } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import { BASE_ALERTING_API_PATH, RuleTypeParams } from '@kbn/alerting-plugin/common';
Expand All @@ -13,14 +14,39 @@ import type {
CreateRuleRequestBody,
CreateRuleResponse,
} from '@kbn/alerting-plugin/common/routes/rule/apis/create';
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { useKibana } from '../utils/kibana_react';

export function useCreateRule<Params extends RuleTypeParams = never>() {
const {
http,
i18n: i18nStart,
notifications: { toasts },
theme,
} = useKibana().services;

let loadingToastId = '';

const showLoadingToast = () => {
const loadingToast = toasts.addInfo({
title: toMountPoint(
<EuiFlexGroup justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>
{i18n.translate('xpack.slo.rules.createRule.loadingNotification.descriptionText', {
defaultMessage: 'Creating burn rate rule ...',
})}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="s" />
</EuiFlexItem>
</EuiFlexGroup>,
{ i18n: i18nStart, theme }
),
});
loadingToastId = loadingToast.id;
};

const createRule = useMutation<
CreateRuleResponse<Params>,
Error,
Expand All @@ -31,6 +57,7 @@ export function useCreateRule<Params extends RuleTypeParams = never>() {
try {
const ruleId = v4();
const body = JSON.stringify(rule);
showLoadingToast();
Comment thread
shahzad31 marked this conversation as resolved.
Outdated
return http.post(`${BASE_ALERTING_API_PATH}/rule/${ruleId}`, {
body,
});
Expand All @@ -40,6 +67,9 @@ export function useCreateRule<Params extends RuleTypeParams = never>() {
},
{
onError: (_err) => {
if (loadingToastId) {
Comment thread
shahzad31 marked this conversation as resolved.
Outdated
toasts.remove(loadingToastId);
}
toasts.addDanger(
i18n.translate('xpack.slo.rules.createRule.errorNotification.descriptionText', {
defaultMessage: 'Failed to create burn rate rule.',
Expand All @@ -48,6 +78,9 @@ export function useCreateRule<Params extends RuleTypeParams = never>() {
},

onSuccess: () => {
if (loadingToastId) {
toasts.remove(loadingToastId);
}
toasts.addSuccess(
i18n.translate('xpack.slo.rules.createRule.successNotification.descriptionText', {
defaultMessage: 'Burn rate rule created successfully.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function useCreateSlo() {
<RedirectAppLinks coreStart={services} data-test-subj="observabilityMainContainer">
<FormattedMessage
id="xpack.slo.slo.create.successNotification"
defaultMessage="Successfully created {name}. {editSLO}"
defaultMessage='Successfully created SLO: "{name}". {editSLO}'
values={{
name: slo.name,
editSLO: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { GetSLOResponse } from '@kbn/slo-schema';
import React, { useCallback, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { InPortal } from 'react-reverse-portal';
import { useCreateRule } from '../../../hooks/use_create_rule';
import { useCreateRule } from '../../../hooks/use_create_burn_rate_rule';
import { useKibana } from '../../../utils/kibana_react';
import { sloEditFormFooterPortal } from '../shared_flyout/slo_add_form_flyout';
import { paths } from '../../../../common/locators/paths';
Expand Down Expand Up @@ -70,8 +70,10 @@ export function SloEditFormFooter({ slo, onSave }: Props) {
} else {
const processedValues = transformCreateSLOFormToCreateSLOInput(values);
const resp = await createSlo({ slo: processedValues });
await createBurnRateRule({
Comment thread
shahzad31 marked this conversation as resolved.
createBurnRateRule({
rule: createBurnRateRuleRequestBody({ ...processedValues, id: resp.id }),
}).catch((e) => {
// we handle error in hook
});
if (onSave) {
onSave();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useFetchApmSuggestions } from '../../hooks/use_fetch_apm_suggestions';
import { useFetchIndices } from '../../hooks/use_fetch_indices';
import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details';
import { usePermissions } from '../../hooks/use_permissions';
import { useCreateRule } from '../../hooks/use_create_rule';
import { useCreateRule } from '../../hooks/use_create_burn_rate_rule';
import { useUpdateSlo } from '../../hooks/use_update_slo';
import { useKibana } from '../../utils/kibana_react';
import { kibanaStartMock } from '../../utils/kibana_react.mock';
Expand All @@ -45,7 +45,7 @@ jest.mock('../../hooks/use_create_slo');
jest.mock('../../hooks/use_update_slo');
jest.mock('../../hooks/use_fetch_apm_suggestions');
jest.mock('../../hooks/use_permissions');
jest.mock('../../hooks/use_create_rule');
jest.mock('../../hooks/use_create_burn_rate_rule');

const mockUseKibanaReturnValue = kibanaStartMock.startContract();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
updateSLOParamsSchema,
} from '@kbn/slo-schema';
import { getOverviewParamsSchema } from '@kbn/slo-schema/src/rest_specs/routes/get_overview';
import { KibanaRequest } from '@kbn/core-http-server';
import { RegisterRoutesDependencies } from '../register_routes';
import { GetSLOsOverview } from '../../services/get_slos_overview';
import type { IndicatorTypes } from '../../domain/models';
import {
Expand Down Expand Up @@ -91,6 +93,11 @@ const assertPlatinumLicense = async (context: SloRequestHandlerContext) => {
}
};

const getSpaceId = async (deps: RegisterRoutesDependencies, request: KibanaRequest) => {
const spaces = await deps.getSpacesStart();
return (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default';
};

const createSLORoute = createSloServerRoute({
endpoint: 'POST /api/observability/slos 2023-10-31',
options: {
Expand All @@ -101,18 +108,18 @@ const createSLORoute = createSloServerRoute({
handler: async ({ context, params, logger, dependencies, request }) => {
await assertPlatinumLicense(context);

const spaces = await dependencies.getSpacesStart();
const dataViews = await dependencies.getDataViewsStart();
const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default';

const core = await context.core;
const scopedClusterClient = core.elasticsearch.client;
const esClient = core.elasticsearch.client.asCurrentUser;
const basePath = dependencies.pluginsSetup.core.http.basePath;
const soClient = core.savedObjects.client;
const repository = new KibanaSavedObjectsSLORepository(soClient, logger);

const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient);
const [spaceId, dataViewsService] = await Promise.all([
getSpaceId(dependencies, request),
dataViews.dataViewsServiceFactory(soClient, esClient),
]);
const transformManager = new DefaultTransformManager(
transformGenerators,
scopedClusterClient,
Expand All @@ -125,7 +132,6 @@ const createSLORoute = createSloServerRoute({
scopedClusterClient,
logger
);

const createSLO = new CreateSLO(
esClient,
scopedClusterClient,
Expand All @@ -137,9 +143,7 @@ const createSLORoute = createSloServerRoute({
basePath
);

const response = await createSLO.execute(params.body);

return response;
return await createSLO.execute(params.body);
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,10 @@ describe('CreateSLO', () => {
);

expect(mockTransformManager.install).toHaveBeenCalled();
expect(mockTransformManager.start).toHaveBeenCalled();
expect(
mockScopedClusterClient.asSecondaryAuthUser.ingest.putPipeline.mock.calls[0]
).toMatchSnapshot();
expect(mockSummaryTransformManager.install).toHaveBeenCalled();
expect(mockSummaryTransformManager.start).toHaveBeenCalled();
expect(mockEsClient.index.mock.calls[0]).toMatchSnapshot();

expect(response).toEqual(expect.objectContaining({ id: 'unique-id' }));
Expand Down Expand Up @@ -173,16 +171,16 @@ describe('CreateSLO', () => {
expect(mockRepository.deleteById).toHaveBeenCalled();
expect(
mockScopedClusterClient.asSecondaryAuthUser.ingest.deletePipeline
).toHaveBeenCalledTimes(1);
).toHaveBeenCalledTimes(2);

expect(mockSummaryTransformManager.stop).not.toHaveBeenCalled();
expect(mockSummaryTransformManager.uninstall).not.toHaveBeenCalled();
expect(mockTransformManager.stop).not.toHaveBeenCalled();
expect(mockTransformManager.uninstall).not.toHaveBeenCalled();
expect(mockSummaryTransformManager.stop).toHaveBeenCalledTimes(1);
expect(mockSummaryTransformManager.uninstall).toHaveBeenCalledTimes(1);
expect(mockTransformManager.stop).toHaveBeenCalledTimes(1);
expect(mockTransformManager.uninstall).toHaveBeenCalledTimes(1);
});

it('rollbacks completed operations when summary transform start fails', async () => {
mockSummaryTransformManager.start.mockRejectedValue(
it('rollbacks completed operations when summary transform install fails', async () => {
mockSummaryTransformManager.install.mockRejectedValue(
new Error('Summary transform install error')
);
const sloParams = createSLOParams({ indicator: createAPMTransactionErrorRateIndicator() });
Expand All @@ -199,7 +197,7 @@ describe('CreateSLO', () => {
).toHaveBeenCalledTimes(2);
expect(mockSummaryTransformManager.uninstall).toHaveBeenCalled();

expect(mockSummaryTransformManager.stop).not.toHaveBeenCalled();
expect(mockSummaryTransformManager.stop).toHaveBeenCalled();
});

it('rollbacks completed operations when create temporary document fails', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ElasticsearchClient, IBasePath, Logger } from '@kbn/core/server';
import { ALL_VALUE, CreateSLOParams, CreateSLOResponse } from '@kbn/slo-schema';
import { asyncForEach } from '@kbn/std';
import { v4 as uuidv4 } from 'uuid';
import { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types';
import {
getSLOPipelineId,
getSLOSummaryPipelineId,
Expand Down Expand Up @@ -47,62 +48,41 @@ export class CreateSLO {

const rollbackOperations = [];

await this.repository.save(slo, { throwOnConflict: true });
rollbackOperations.push(() => this.repository.deleteById(slo.id));
// can be done async as we are not using the result
void this.repository.save(slo, { throwOnConflict: true });
Comment thread
shahzad31 marked this conversation as resolved.
Outdated
rollbackOperations.push(() => this.repository.deleteById(slo.id, true));

const rollupTransformId = getSLOTransformId(slo.id, slo.revision);
const summaryTransformId = getSLOSummaryTransformId(slo.id, slo.revision);
try {
await retryTransientEsErrors(
() =>
this.scopedClusterClient.asSecondaryAuthUser.ingest.putPipeline(
getSLOPipelineTemplate(slo)
),
{ logger: this.logger }
);
rollbackOperations.push(() =>
this.scopedClusterClient.asSecondaryAuthUser.ingest.deletePipeline(
{ id: getSLOPipelineId(slo.id, slo.revision) },
{ ignore: [404] }
)
);
const sloPipelinePromise = this.createPipeline(getSLOPipelineTemplate(slo));
rollbackOperations.push(() => this.deletePipeline(getSLOPipelineId(slo.id, slo.revision)));

await this.transformManager.install(slo);
const rollupTransformPromise = this.transformManager.install(slo, true);
rollbackOperations.push(() => this.transformManager.uninstall(rollupTransformId));

await this.transformManager.start(rollupTransformId);
rollbackOperations.push(() => this.transformManager.stop(rollupTransformId));

await retryTransientEsErrors(
() =>
this.scopedClusterClient.asSecondaryAuthUser.ingest.putPipeline(
getSLOSummaryPipelineTemplate(slo, this.spaceId, this.basePath)
),
{ logger: this.logger }
const summaryPipelinePromise = this.createPipeline(
getSLOSummaryPipelineTemplate(slo, this.spaceId, this.basePath)
);

rollbackOperations.push(() =>
this.scopedClusterClient.asSecondaryAuthUser.ingest.deletePipeline(
{ id: getSLOSummaryPipelineId(slo.id, slo.revision) },
{ ignore: [404] }
)
this.deletePipeline(getSLOSummaryPipelineId(slo.id, slo.revision))
);

await this.summaryTransformManager.install(slo);
const summaryTransformPromise = this.summaryTransformManager.install(slo, true);
rollbackOperations.push(() => this.summaryTransformManager.uninstall(summaryTransformId));

await this.summaryTransformManager.start(summaryTransformId);
rollbackOperations.push(() => this.summaryTransformManager.stop(summaryTransformId));
Comment thread
shahzad31 marked this conversation as resolved.
Outdated

await retryTransientEsErrors(
() =>
this.esClient.index({
index: SLO_SUMMARY_TEMP_INDEX_NAME,
id: `slo-${slo.id}`,
document: createTempSummaryDocument(slo, this.spaceId, this.basePath),
refresh: true,
}),
{ logger: this.logger }
);
const tempDocPromise = this.createTempSummaryDocument(slo);

await Promise.all([
sloPipelinePromise,
rollupTransformPromise,
summaryPipelinePromise,
summaryTransformPromise,
tempDocPromise,
Comment thread
kdelemme marked this conversation as resolved.
]);
} catch (err) {
this.logger.error(
`Cannot install the SLO [id: ${slo.id}, revision: ${slo.revision}]. Rolling back.`
Expand All @@ -126,6 +106,33 @@ export class CreateSLO {
return this.toResponse(slo);
}

async createTempSummaryDocument(slo: SLODefinition) {
return await retryTransientEsErrors(
() =>
this.esClient.index({
index: SLO_SUMMARY_TEMP_INDEX_NAME,
id: `slo-${slo.id}`,
document: createTempSummaryDocument(slo, this.spaceId, this.basePath),
refresh: true,
}),
{ logger: this.logger }
);
}

async createPipeline(params: IngestPutPipelineRequest) {
return await retryTransientEsErrors(
() => this.scopedClusterClient.asSecondaryAuthUser.ingest.putPipeline(params),
{ logger: this.logger }
);
}

async deletePipeline(id: string) {
return this.scopedClusterClient.asSecondaryAuthUser.ingest.deletePipeline(
{ id },
{ ignore: [404] }
);
}

public async inspect(params: CreateSLOParams): Promise<{
slo: CreateSLOParams;
rollUpPipeline: Record<string, any>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface SLORepository {
save(slo: SLODefinition, options?: { throwOnConflict: boolean }): Promise<SLODefinition>;
findAllByIds(ids: string[]): Promise<SLODefinition[]>;
findById(id: string): Promise<SLODefinition>;
deleteById(id: string): Promise<void>;
deleteById(id: string, ignore404?: boolean): Promise<void>;
search(
search: string,
pagination: Pagination,
Expand Down Expand Up @@ -73,7 +73,7 @@ export class KibanaSavedObjectsSLORepository implements SLORepository {
return slo;
}

async deleteById(id: string): Promise<void> {
async deleteById(id: string, ignore404 = false): Promise<void> {
Comment thread
shahzad31 marked this conversation as resolved.
Outdated
const response = await this.soClient.find<StoredSLODefinition>({
type: SO_SLO_TYPE,
page: 1,
Expand All @@ -82,6 +82,9 @@ export class KibanaSavedObjectsSLORepository implements SLORepository {
});

if (response.total === 0) {
if (ignore404) {
return;
}
throw new SLONotFound(`SLO [${id}] not found`);
}

Expand Down
Loading