Skip to content

Commit

Permalink
Add Locator for Rules page (#155799)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
CoenWarmer and kibanamachine authored Apr 26, 2023
1 parent 559d928 commit 53daa33
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 17 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/observability/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const syntheticsMonitorDetailLocatorID = 'SYNTHETICS_MONITOR_DETAIL_LOCAT
export const syntheticsEditMonitorLocatorID = 'SYNTHETICS_EDIT_MONITOR_LOCATOR';
export const syntheticsSettingsLocatorID = 'SYNTHETICS_SETTINGS';
export const ruleDetailsLocatorID = 'RULE_DETAILS_LOCATOR';
export const rulesLocatorID = 'RULES_LOCATOR';

export {
NETWORK_TIMINGS_FIELDS,
Expand Down
55 changes: 55 additions & 0 deletions x-pack/plugins/observability/public/locators/rules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { RulesLocatorDefinition, RULES_PATH } from './rules';

describe('RulesLocator', () => {
const locator = new RulesLocatorDefinition();

it('should return correct url when empty params are provided', async () => {
const location = await locator.getLocation({});
expect(location.app).toEqual('observability');
expect(location.path).toEqual(
`${RULES_PATH}?_a=(lastResponse:!(),params:(),search:'',status:!(),type:!())`
);
});

it('should return correct url when lastResponse is provided', async () => {
const location = await locator.getLocation({ lastResponse: ['foo'] });
expect(location.path).toEqual(
`${RULES_PATH}?_a=(lastResponse:!(foo),params:(),search:'',status:!(),type:!())`
);
});

it('should return correct url when params is provided', async () => {
const location = await locator.getLocation({ params: { sloId: 'foo' } });
expect(location.path).toEqual(
`${RULES_PATH}?_a=(lastResponse:!(),params:(sloId:foo),search:'',status:!(),type:!())`
);
});

it('should return correct url when search is provided', async () => {
const location = await locator.getLocation({ search: 'foo' });
expect(location.path).toEqual(
`${RULES_PATH}?_a=(lastResponse:!(),params:(),search:foo,status:!(),type:!())`
);
});

it('should return correct url when status is provided', async () => {
const location = await locator.getLocation({ status: ['enabled'] });
expect(location.path).toEqual(
`${RULES_PATH}?_a=(lastResponse:!(),params:(),search:'',status:!(enabled),type:!())`
);
});

it('should return correct url when type is provided', async () => {
const location = await locator.getLocation({ type: ['foo'] });
expect(location.path).toEqual(
`${RULES_PATH}?_a=(lastResponse:!(),params:(),search:'',status:!(),type:!(foo))`
);
});
});
54 changes: 54 additions & 0 deletions x-pack/plugins/observability/public/locators/rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
import type { SerializableRecord } from '@kbn/utility-types';
import type { LocatorDefinition } from '@kbn/share-plugin/public';
import type { RuleStatus } from '@kbn/triggers-actions-ui-plugin/public';
import { rulesLocatorID } from '../../common';

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type RulesParams = {
lastResponse?: string[];
params?: Record<string, string | number>;
search?: string;
status?: RuleStatus[];
type?: string[];
};

export interface RulesLocatorParams extends RulesParams, SerializableRecord {}

export const RULES_PATH = '/alerts/rules';

export class RulesLocatorDefinition implements LocatorDefinition<RulesLocatorParams> {
public readonly id = rulesLocatorID;

public readonly getLocation = async ({
lastResponse = [],
params = {},
search = '',
status = [],
type = [],
}: RulesLocatorParams) => {
return {
app: 'observability',
path: setStateToKbnUrl(
'_a',
{
lastResponse,
params,
search,
status,
type,
},
{ useHash: false, storeInHashQuery: false },
RULES_PATH
),
state: {},
};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';

import { useCapabilities } from '../../../hooks/slo/use_capabilities';
import { useKibana } from '../../../utils/kibana_react';
import { isApmIndicatorType } from '../../../utils/slo/indicator';
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants';
import { sloFeatureId } from '../../../../common';
import { rulesLocatorID, sloFeatureId } from '../../../../common';
import { paths } from '../../../config/paths';
import { useKibana } from '../../../utils/kibana_react';
import { ObservabilityAppServices } from '../../../application/types';
import { useCapabilities } from '../../../hooks/slo/use_capabilities';
import type { RulesParams } from '../../../locators/rules';

export interface Props {
slo: SLOWithSummaryResponse | undefined;
Expand All @@ -28,8 +28,11 @@ export function HeaderControl({ isLoading, slo }: Props) {
const {
application: { navigateToUrl },
http: { basePath },
share: {
url: { locators },
},
triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout },
} = useKibana<ObservabilityAppServices>().services;
} = useKibana().services;
const { hasWriteCapabilities } = useCapabilities();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState<boolean>(false);
Expand All @@ -52,12 +55,19 @@ export function HeaderControl({ isLoading, slo }: Props) {
setRuleFlyoutVisibility(true);
};

const handleNavigateToRules = () => {
navigateToUrl(
basePath.prepend(
`${paths.observability.rules}?_a=(lastResponse:!(),search:%27%27,params:(sloId:%27${slo?.id}%27),status:!(),type:!())`
)
);
const handleNavigateToRules = async () => {
const locator = locators.get<RulesParams>(rulesLocatorID);

if (slo?.id) {
locator?.navigate(
{
params: { sloId: slo.id },
},
{
replace: true,
}
);
}
};

const handleNavigateToApm = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const useCapabilitiesMock = useCapabilities as jest.Mock;

const mockNavigate = jest.fn();
const mockBasePathPrepend = jest.fn();
const mockLocator = jest.fn();

const mockKibana = () => {
useKibanaMock.mockReturnValue({
Expand All @@ -60,6 +61,13 @@ const mockKibana = () => {
prepend: mockBasePathPrepend,
},
},
share: {
url: {
locators: {
get: mockLocator,
},
},
},
triggersActionsUi: {
getAddRuleFlyout: jest.fn(() => (
<div data-test-subj="add-rule-flyout">mocked component</div>
Expand Down Expand Up @@ -182,6 +190,24 @@ describe('SLO Details Page', () => {
expect(screen.queryByTestId('sloDetailsHeaderControlPopoverCreateRule')).toBeTruthy();
});

it("renders a 'Manage rules' button under actions menu", async () => {
const slo = buildSlo();
useParamsMock.mockReturnValue(slo.id);
useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo });
useLicenseMock.mockReturnValue({ hasAtLeast: () => true });

render(<SloDetailsPage />);

fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton'));

const manageRulesButton = screen.queryByTestId('sloDetailsHeaderControlPopoverManageRules');
expect(manageRulesButton).toBeTruthy();

fireEvent.click(manageRulesButton!);

expect(mockLocator).toBeCalled();
});

it('renders the Overview tab by default', async () => {
const slo = buildSlo();
useParamsMock.mockReturnValue(slo.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ import {
transformValuesToCreateSLOInput,
} from '../../slo_edit/helpers/process_slo_form_values';
import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants';
import { sloFeatureId } from '../../../../common';
import { rulesLocatorID, sloFeatureId } from '../../../../common';
import { paths } from '../../../config/paths';
import type { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts';
import type { SloRule } from '../../../hooks/slo/use_fetch_rules_for_slo';
import type { RulesParams } from '../../../locators/rules';

export interface SloListItemProps {
slo: SLOWithSummaryResponse;
Expand All @@ -57,6 +58,9 @@ export function SloListItem({
const {
application: { navigateToUrl },
http: { basePath },
share: {
url: { locators },
},
triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout },
} = useKibana().services;
const { hasWriteCapabilities } = useCapabilities();
Expand Down Expand Up @@ -92,11 +96,16 @@ export function SloListItem({
queryClient.invalidateQueries(['fetchRulesForSlo']);
};

const handleNavigateToRules = () => {
navigateToUrl(
basePath.prepend(
`${paths.observability.rules}?_a=(lastResponse:!(),search:%27%27,params:(sloId:%27${slo?.id}%27),status:!(),type:!())`
)
const handleNavigateToRules = async () => {
const locator = locators.get<RulesParams>(rulesLocatorID);

locator?.navigate(
{
params: { sloId: slo.id },
},
{
replace: true,
}
);
};

Expand Down
33 changes: 33 additions & 0 deletions x-pack/plugins/observability/public/pages/slos/slos.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ useDeleteSloMock.mockReturnValue({ mutate: mockDeleteSlo });
const mockNavigate = jest.fn();
const mockAddSuccess = jest.fn();
const mockAddError = jest.fn();
const mockLocator = jest.fn();
const mockGetAddRuleFlyout = jest.fn().mockReturnValue(() => <div>Add rule flyout</div>);

const mockKibana = () => {
Expand All @@ -78,6 +79,13 @@ const mockKibana = () => {
addError: mockAddError,
},
},
share: {
url: {
locators: {
get: mockLocator,
},
},
},
triggersActionsUi: { getAddRuleFlyout: mockGetAddRuleFlyout },
uiSettings: {
get: (settings: string) => {
Expand Down Expand Up @@ -226,6 +234,31 @@ describe('SLOs Page', () => {
expect(mockGetAddRuleFlyout).toBeCalled();
});

it('allows managing rules for an SLO', async () => {
useFetchSloListMock.mockReturnValue({ isLoading: false, sloList });

useFetchHistoricalSummaryMock.mockReturnValue({
isLoading: false,
sloHistoricalSummaryResponse: historicalSummaryData,
});

await act(async () => {
render(<SlosPage />);
});

screen.getAllByLabelText('Actions').at(0)?.click();

await waitForEuiPopoverOpen();

const button = screen.getByTestId('sloActionsManageRules');

expect(button).toBeTruthy();

button.click();

expect(mockLocator).toBeCalled();
});

it('allows deleting an SLO', async () => {
useFetchSloListMock.mockReturnValue({ isLoading: false, sloList });

Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/observability/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { ExploratoryViewPublicStart } from '@kbn/exploratory-view-plugin/public';
import { RuleDetailsLocatorDefinition } from './locators/rule_details';
import { RulesLocatorDefinition } from './locators/rules';
import { observabilityAppId, observabilityFeatureId, casesPath } from '../common';
import { registerDataHandler } from './data_handler';
import {
Expand Down Expand Up @@ -190,6 +191,9 @@ export class Plugin
this.observabilityRuleTypeRegistry = createObservabilityRuleTypeRegistry(
pluginsSetup.triggersActionsUi.ruleTypeRegistry
);

const locator = pluginsSetup.share.url.locators.create(new RulesLocatorDefinition());

pluginsSetup.share.url.locators.create(new RuleDetailsLocatorDefinition());

const mount = async (params: AppMountParameters<unknown>) => {
Expand Down Expand Up @@ -310,6 +314,7 @@ export class Plugin
dashboard: { register: registerDataHandler },
observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry,
useRulesLink: createUseRulesLink(),
rulesLocator: locator,
};
}

Expand Down

0 comments on commit 53daa33

Please sign in to comment.