Skip to content

Commit

Permalink
[SLO] SLO List (#147447)
Browse files Browse the repository at this point in the history
Closes #146892
  • Loading branch information
CoenWarmer authored Dec 20, 2022
1 parent c673938 commit 3cfada5
Show file tree
Hide file tree
Showing 43 changed files with 2,403 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
* 2.0.
*/

import { asDecimal, asInteger, asPercent, asDecimalOrInteger } from './formatters';
import {
asDecimal,
asInteger,
asPercent,
asPercentWithTwoDecimals,
asDecimalOrInteger,
} from './formatters';

describe('formatters', () => {
describe('asDecimal', () => {
Expand Down Expand Up @@ -89,6 +95,33 @@ describe('formatters', () => {
});
});

describe('asPercentWithTwoDecimals', () => {
it('formats as integer when number is above 10', () => {
expect(asPercentWithTwoDecimals(3725, 10000, 'n/a')).toEqual('37.25%');
});

it('adds a decimal when value is below 10', () => {
expect(asPercentWithTwoDecimals(0.092, 1)).toEqual('9.20%');
});

it('formats when numerator is 0', () => {
expect(asPercentWithTwoDecimals(0, 1, 'n/a')).toEqual('0%');
});

it('returns fallback when denominator is undefined', () => {
expect(asPercentWithTwoDecimals(3725, undefined, 'n/a')).toEqual('n/a');
});

it('returns fallback when denominator is 0 ', () => {
expect(asPercentWithTwoDecimals(3725, 0, 'n/a')).toEqual('n/a');
});

it('returns fallback when numerator or denominator is NaN', () => {
expect(asPercentWithTwoDecimals(3725, NaN, 'n/a')).toEqual('n/a');
expect(asPercentWithTwoDecimals(NaN, 10000, 'n/a')).toEqual('n/a');
});
});

describe('asDecimalOrInteger', () => {
it('formats as integer when number equals to 0 ', () => {
expect(asDecimalOrInteger(0)).toEqual('0');
Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/observability/common/utils/formatters/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,27 @@ export function asPercent(
return numeral(decimal).format('0.0%');
}

export function asPercentWithTwoDecimals(
numerator: Maybe<number>,
denominator: number | undefined,
fallbackResult = NOT_AVAILABLE_LABEL
) {
if (!denominator || !isFiniteNumber(numerator)) {
return fallbackResult;
}

const decimal = numerator / denominator;

// 33.2 => 33.20%
// 3.32 => 3.32%
// 0 => 0%
if (String(Math.abs(decimal)).split('.').at(1)?.length === 2 || decimal === 0 || decimal === 1) {
return numeral(decimal).format('0%');
}

return numeral(decimal).format('0.00%');
}

export type AsPercent = typeof asPercent;

export function asDecimalOrInteger(value: number) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('SLO Selector', () => {
render(<SloSelector onSelected={onSelectedSpy} />);

expect(screen.getByTestId('sloSelector')).toBeTruthy();
expect(useFetchSloListMock).toHaveBeenCalledWith('');
expect(useFetchSloListMock).toHaveBeenCalledWith({ name: '', refetch: false });
});

it('searches SLOs when typing', async () => {
Expand All @@ -41,6 +41,6 @@ describe('SLO Selector', () => {
await wait(310); // debounce delay
});

expect(useFetchSloListMock).toHaveBeenCalledWith('latency');
expect(useFetchSloListMock).toHaveBeenCalledWith({ name: 'latency', refetch: false });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function SloSelector({ onSelected }: Props) {
const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<string>>>([]);
const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>();
const [searchValue, setSearchValue] = useState<string>('');
const { loading, sloList } = useFetchSloList(searchValue);
const { loading, sloList } = useFetchSloList({ name: searchValue, refetch: false });

useEffect(() => {
const isLoadedWithData = !loading && sloList !== undefined;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/observability/public/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const paths = {
ruleDetails: (ruleId?: string | null) =>
ruleId ? `${RULES_PAGE_LINK}/${encodeURI(ruleId)}` : RULES_PAGE_LINK,
slos: SLOS_PAGE_LINK,
sloDetails: (sloId: string) => `${SLOS_PAGE_LINK}/${encodeURI(sloId)}`,
},
management: {
rules: '/app/management/insightsAndAlerting/triggersActions/rules',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UseFetchSloListResponse } from '../use_fetch_slo_list';
export const useFetchSloList = (name?: string): UseFetchSloListResponse => {
return {
loading: false,
error: false,
sloList,
};
};
46 changes: 46 additions & 0 deletions x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { useCallback, useState } from 'react';
import { useKibana } from '../../utils/kibana_react';

interface UseDeleteSlo {
loading: boolean;
success: boolean;
error: string | undefined;
deleteSlo: (id: string) => void;
}

export function useDeleteSlo(): UseDeleteSlo {
const { http } = useKibana().services;
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);

const deleteSlo = useCallback(
async (id: string) => {
setLoading(true);
setError('');
setSuccess(false);

try {
await http.delete<string>(`/api/observability/slos/${id}`);
setSuccess(true);
} catch (e) {
setError(e);
}
},
[http]
);

return {
loading,
error,
success,
deleteSlo,
};
}
47 changes: 31 additions & 16 deletions x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
* 2.0.
*/

import { useCallback, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { HttpSetup } from '@kbn/core/public';

import type { SLOList } from '../../typings/slo';
import { useDataFetcher } from '../use_data_fetcher';
import { toSLO } from '../../utils/slo/slo';

const EMPTY_LIST = {
const EMPTY_LIST: SLOList = {
results: [],
total: 0,
page: 0,
Expand All @@ -21,29 +21,46 @@ const EMPTY_LIST = {

interface SLOListParams {
name?: string;
page?: number;
}

interface UseFetchSloListResponse {
loading: boolean;
export interface UseFetchSloListResponse {
sloList: SLOList;
loading: boolean;
error: boolean;
}

const useFetchSloList = (name?: string): UseFetchSloListResponse => {
const params: SLOListParams = useMemo(() => ({ name }), [name]);
export function useFetchSloList({
name,
refetch,
page,
}: {
refetch: boolean;
name?: string;
page?: number;
}): UseFetchSloListResponse {
const [sloList, setSloList] = useState(EMPTY_LIST);

const params: SLOListParams = useMemo(() => ({ name, page }), [name, page]);
const shouldExecuteApiCall = useCallback(
(apiCallParams: SLOListParams) => apiCallParams.name === params.name,
[params]
(apiCallParams: SLOListParams) =>
apiCallParams.name === params.name || apiCallParams.page === params.page || refetch,
[params, refetch]
);

const { loading, data: sloList } = useDataFetcher<SLOListParams, SLOList>({
const { data, loading, error } = useDataFetcher<SLOListParams, SLOList>({
paramsForApiCall: params,
initialDataState: EMPTY_LIST,
initialDataState: sloList,
executeApiCall: fetchSloList,
shouldExecuteApiCall,
});

return { loading, sloList };
};
useEffect(() => {
setSloList(data);
}, [data]);

return { sloList, loading, error };
}

const fetchSloList = async (
params: SLOListParams,
Expand All @@ -53,6 +70,7 @@ const fetchSloList = async (
try {
const response = await http.get<Record<string, unknown>>(`/api/observability/slos`, {
query: {
...(params.page && { page: params.page }),
...(params.name && { name: params.name }),
},
signal: abortController.signal,
Expand All @@ -73,12 +91,9 @@ function toSLOList(response: Record<string, unknown>): SLOList {
}

return {
results: response.results.map((result) => toSLO(result)),
results: response.results.map(toSLO),
page: Number(response.page),
perPage: Number(response.per_page),
total: Number(response.total),
};
}

export { useFetchSloList };
export type { UseFetchSloListResponse };
16 changes: 12 additions & 4 deletions x-pack/plugins/observability/public/hooks/use_data_fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const useDataFetcher = <ApiCallParams, AlertDataType>({
}) => {
const { http } = useKibana().services;
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [data, setData] = useState<AlertDataType>(initialDataState);

const { fetch, cancel } = useMemo(() => {
Expand All @@ -34,12 +35,18 @@ export const useDataFetcher = <ApiCallParams, AlertDataType>({
return {
fetch: async () => {
if (shouldExecuteApiCall(paramsForApiCall)) {
setError(false);
setLoading(true);

const results = await executeApiCall(paramsForApiCall, abortController, http);
if (!isCanceled) {
try {
const results = await executeApiCall(paramsForApiCall, abortController, http);
if (!isCanceled) {
setLoading(false);
setData(results);
}
} catch (e) {
setError(true);
setLoading(false);
setData(results);
}
}
},
Expand All @@ -59,7 +66,8 @@ export const useDataFetcher = <ApiCallParams, AlertDataType>({
}, [fetch, cancel]);

return {
loading,
data,
loading,
error,
};
};
Loading

0 comments on commit 3cfada5

Please sign in to comment.