mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[RAM][Flapping] Make rules settings link with flapping settings shareable (#149564)
## Summary Resolves: https://github.com/elastic/kibana/issues/148760 Makes the rules setting link that opens up the flapping settings modal shareable from the `triggers_action_ui` plugin (`getRulesSettingsLink`). Also adds storybook entires for this component (`rulesSettingsLink`). To view locally, run `yarn storybook triggers_actions_ui` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
This commit is contained in:
parent
e2e58635a3
commit
7608dfb023
17 changed files with 288 additions and 39 deletions
|
@ -27,6 +27,7 @@ import { RuleEventLogListSandbox } from './components/rule_event_log_list_sandbo
|
|||
import { RuleStatusDropdownSandbox } from './components/rule_status_dropdown_sandbox';
|
||||
import { RuleStatusFilterSandbox } from './components/rule_status_filter_sandbox';
|
||||
import { AlertsTableSandbox } from './components/alerts_table_sandbox';
|
||||
import { RulesSettingsLinkSandbox } from './components/rules_settings_link_sandbox';
|
||||
|
||||
export interface TriggersActionsUiExampleComponentParams {
|
||||
http: CoreStart['http'];
|
||||
|
@ -124,6 +125,14 @@ const TriggersActionsUiExampleApp = ({
|
|||
</Page>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/rules_settings_link"
|
||||
render={() => (
|
||||
<Page title="Rules Settings Link">
|
||||
<RulesSettingsLinkSandbox triggersActionsUi={triggersActionsUi} />
|
||||
</Page>
|
||||
)}
|
||||
/>
|
||||
</EuiPage>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
interface SandboxProps {
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export const RulesSettingsLinkSandbox = ({ triggersActionsUi }: SandboxProps) => {
|
||||
return <div style={{ flex: 1 }}>{triggersActionsUi.getRulesSettingsLink()}</div>;
|
||||
};
|
|
@ -64,6 +64,11 @@ export const Sidebar = () => {
|
|||
name: 'Alert Table',
|
||||
onClick: () => history.push('/alerts_table'),
|
||||
},
|
||||
{
|
||||
id: 'rules settings link',
|
||||
name: 'Rules Settings Link',
|
||||
onClick: () => history.push('/rules_settings_link'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { action } from '@storybook/addon-actions';
|
||||
import { of } from 'rxjs';
|
||||
import type { ApplicationStart } from '@kbn/core/public';
|
||||
import { getDefaultCapabilities } from './capabilities';
|
||||
|
||||
export const getDefaultServicesApplication = (
|
||||
override?: Partial<ApplicationStart>
|
||||
): ApplicationStart => {
|
||||
const applications = new Map();
|
||||
|
||||
return {
|
||||
currentAppId$: of('fleet'),
|
||||
navigateToUrl: async (url: string) => {
|
||||
action(`Navigate to: ${url}`);
|
||||
},
|
||||
navigateToApp: async (app: string) => {
|
||||
action(`Navigate to: ${app}`);
|
||||
},
|
||||
getUrlForApp: (url: string) => url,
|
||||
capabilities: getDefaultCapabilities(),
|
||||
applications$: of(applications),
|
||||
...override,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 type { Capabilities } from '@kbn/core-capabilities-common';
|
||||
|
||||
export const getDefaultCapabilities = (override?: Partial<Capabilities>): Capabilities => {
|
||||
return {
|
||||
actions: {
|
||||
show: true,
|
||||
save: true,
|
||||
execute: true,
|
||||
delete: true,
|
||||
},
|
||||
catalogue: {},
|
||||
management: {},
|
||||
navLinks: {},
|
||||
fleet: {
|
||||
read: true,
|
||||
all: true,
|
||||
},
|
||||
fleetv2: {
|
||||
read: true,
|
||||
all: true,
|
||||
},
|
||||
...override,
|
||||
};
|
||||
};
|
|
@ -275,6 +275,22 @@ const paginatedEventLogListGetResponse = (path: string) => {
|
|||
return baseEventLogListGetResponse(path);
|
||||
};
|
||||
|
||||
const rulesSettingsGetResponse = (path: string) => {
|
||||
if (path.endsWith('/settings/_flapping')) {
|
||||
return {
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const rulesSettingsIds = [
|
||||
'app-rulessettingslink--with-all-permission',
|
||||
'app-rulessettingslink--with-read-permission',
|
||||
'app-rulessettingslink--with-no-permission',
|
||||
];
|
||||
|
||||
export const getHttp = (context: Parameters<DecoratorFn>[1]) => {
|
||||
return {
|
||||
get: (async (path: string, options: HttpFetchOptions) => {
|
||||
|
@ -297,9 +313,13 @@ export const getHttp = (context: Parameters<DecoratorFn>[1]) => {
|
|||
if (id === 'app-ruleeventloglist--with-paginated-events') {
|
||||
return paginatedEventLogListGetResponse(path);
|
||||
}
|
||||
if (rulesSettingsIds.includes(id)) {
|
||||
return rulesSettingsGetResponse(path);
|
||||
}
|
||||
}) as HttpHandler,
|
||||
post: (async (path: string, options: HttpFetchOptions) => {
|
||||
action('POST')(path, options);
|
||||
return Promise.resolve();
|
||||
}) as HttpHandler,
|
||||
} as unknown as HttpStart;
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { action } from '@storybook/addon-actions';
|
|||
import { DecoratorFn } from '@storybook/react';
|
||||
import { EMPTY, of } from 'rxjs';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { KibanaThemeProvider, KibanaServices } from '@kbn/kibana-react-plugin/public';
|
||||
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
||||
import type { NotificationsStart, ApplicationStart } from '@kbn/core/public';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
@ -20,9 +20,12 @@ import { ExperimentalFeaturesService } from '../public/common/experimental_featu
|
|||
import { getHttp } from './context/http';
|
||||
import { getRuleTypeRegistry } from './context/rule_type_registry';
|
||||
import { getActionTypeRegistry } from './context/action_type_registry';
|
||||
import { getDefaultServicesApplication } from './context/application';
|
||||
|
||||
interface StorybookContextDecoratorProps {
|
||||
context: Parameters<DecoratorFn>[1];
|
||||
servicesApplicationOverride?: Partial<ApplicationStart>;
|
||||
servicesOverride?: Partial<KibanaServices>;
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
@ -45,41 +48,8 @@ const notifications: NotificationsStart = {
|
|||
},
|
||||
};
|
||||
|
||||
const applications = new Map();
|
||||
|
||||
const application: ApplicationStart = {
|
||||
currentAppId$: of('fleet'),
|
||||
navigateToUrl: async (url: string) => {
|
||||
action(`Navigate to: ${url}`);
|
||||
},
|
||||
navigateToApp: async (app: string) => {
|
||||
action(`Navigate to: ${app}`);
|
||||
},
|
||||
getUrlForApp: (url: string) => url,
|
||||
capabilities: {
|
||||
actions: {
|
||||
show: true,
|
||||
save: true,
|
||||
execute: true,
|
||||
delete: true,
|
||||
},
|
||||
catalogue: {},
|
||||
management: {},
|
||||
navLinks: {},
|
||||
fleet: {
|
||||
read: true,
|
||||
all: true,
|
||||
},
|
||||
fleetv2: {
|
||||
read: true,
|
||||
all: true,
|
||||
},
|
||||
},
|
||||
applications$: of(applications),
|
||||
};
|
||||
|
||||
export const StorybookContextDecorator: React.FC<StorybookContextDecoratorProps> = (props) => {
|
||||
const { children, context } = props;
|
||||
const { children, context, servicesApplicationOverride, servicesOverride } = props;
|
||||
const { globals } = context;
|
||||
const { euiTheme } = globals;
|
||||
|
||||
|
@ -113,10 +83,11 @@ export const StorybookContextDecorator: React.FC<StorybookContextDecoratorProps>
|
|||
}
|
||||
},
|
||||
},
|
||||
application,
|
||||
application: getDefaultServicesApplication(servicesApplicationOverride),
|
||||
http: getHttp(context),
|
||||
actionTypeRegistry: getActionTypeRegistry(),
|
||||
ruleTypeRegistry: getRuleTypeRegistry(),
|
||||
...servicesOverride,
|
||||
}}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 React, { ComponentProps } from 'react';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { RulesSettingsLink } from './rules_settings_link';
|
||||
import { StorybookContextDecorator } from '../../../../.storybook/decorator';
|
||||
import { getDefaultCapabilities } from '../../../../.storybook/context/capabilities';
|
||||
|
||||
type Args = ComponentProps<typeof RulesSettingsLink>;
|
||||
|
||||
export default {
|
||||
title: 'app/RulesSettingsLink',
|
||||
component: RulesSettingsLink,
|
||||
} as Meta<Args>;
|
||||
|
||||
const Template: Story<Args> = () => {
|
||||
return <RulesSettingsLink />;
|
||||
};
|
||||
|
||||
export const withAllPermission = Template.bind({});
|
||||
|
||||
withAllPermission.decorators = [
|
||||
(StoryComponent, context) => (
|
||||
<StorybookContextDecorator
|
||||
context={context}
|
||||
servicesApplicationOverride={{
|
||||
capabilities: getDefaultCapabilities({
|
||||
rulesSettings: {
|
||||
show: true,
|
||||
save: true,
|
||||
readFlappingSettingsUI: true,
|
||||
writeFlappingSettingsUI: true,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<StoryComponent />
|
||||
</StorybookContextDecorator>
|
||||
),
|
||||
];
|
||||
|
||||
export const withReadPermission = Template.bind({});
|
||||
|
||||
withReadPermission.decorators = [
|
||||
(StoryComponent, context) => (
|
||||
<StorybookContextDecorator
|
||||
context={context}
|
||||
servicesApplicationOverride={{
|
||||
capabilities: getDefaultCapabilities({
|
||||
rulesSettings: {
|
||||
show: true,
|
||||
save: false,
|
||||
readFlappingSettingsUI: true,
|
||||
writeFlappingSettingsUI: false,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<StoryComponent />
|
||||
</StorybookContextDecorator>
|
||||
),
|
||||
];
|
||||
|
||||
export const withNoPermission = Template.bind({});
|
||||
|
||||
withNoPermission.decorators = [
|
||||
(StoryComponent, context) => (
|
||||
<StorybookContextDecorator
|
||||
context={context}
|
||||
servicesApplicationOverride={{
|
||||
capabilities: getDefaultCapabilities({
|
||||
rulesSettings: {
|
||||
show: false,
|
||||
save: false,
|
||||
readFlappingSettingsUI: false,
|
||||
writeFlappingSettingsUI: false,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<EuiCallOut title="No Permissions">
|
||||
When the user does not have capabilities to view rules settings, the entire link is hidden
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
<StoryComponent />
|
||||
</StorybookContextDecorator>
|
||||
),
|
||||
];
|
|
@ -39,3 +39,6 @@ export const RulesSettingsLink = () => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { RulesSettingsLink as default };
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 React, { lazy, Suspense } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const RulesSettingsLinkLazy: React.FC = lazy(
|
||||
() => import('../application/components/rules_setting/rules_settings_link')
|
||||
);
|
||||
|
||||
export const getRulesSettingsLinkLazy = () => {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<RulesSettingsLinkLazy />
|
||||
</Suspense>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
|
@ -46,6 +46,7 @@ import { getAlertSummaryWidgetLazy } from './common/get_rule_alerts_summary';
|
|||
import { getRuleDefinitionLazy } from './common/get_rule_definition';
|
||||
import { getRuleStatusPanelLazy } from './common/get_rule_status_panel';
|
||||
import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal';
|
||||
import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link';
|
||||
|
||||
function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
||||
const actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
|
||||
|
@ -133,6 +134,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
|||
getRuleSnoozeModal: (props) => {
|
||||
return getRuleSnoozeModalLazy(props);
|
||||
},
|
||||
getRulesSettingsLink: () => {
|
||||
return getRulesSettingsLinkLazy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ import { AlertSummaryWidgetProps } from './application/sections/rule_details/com
|
|||
import { getAlertSummaryWidgetLazy } from './common/get_rule_alerts_summary';
|
||||
import { RuleSnoozeModalProps } from './application/sections/rules_list/components/rule_snooze_modal';
|
||||
import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal';
|
||||
import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link';
|
||||
|
||||
export interface TriggersAndActionsUIPublicPluginSetup {
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
|
@ -130,6 +131,7 @@ export interface TriggersAndActionsUIPublicPluginStart {
|
|||
getRuleStatusPanel: (props: RuleStatusPanelProps) => ReactElement<RuleStatusPanelProps>;
|
||||
getAlertSummaryWidget: (props: AlertSummaryWidgetProps) => ReactElement<AlertSummaryWidgetProps>;
|
||||
getRuleSnoozeModal: (props: RuleSnoozeModalProps) => ReactElement<RuleSnoozeModalProps>;
|
||||
getRulesSettingsLink: () => ReactElement;
|
||||
}
|
||||
|
||||
interface PluginsSetup {
|
||||
|
@ -433,6 +435,9 @@ export class Plugin
|
|||
getRuleSnoozeModal: (props: RuleSnoozeModalProps) => {
|
||||
return getRuleSnoozeModalLazy(props);
|
||||
},
|
||||
getRulesSettingsLink: () => {
|
||||
return getRulesSettingsLinkLazy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"@kbn/core-doc-links-browser",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/datemath",
|
||||
"@kbn/core-capabilities-common",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { FtrProviderContext } from '../../../../test/functional/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ loadTestFile, getService }: FtrProviderContext) => {
|
||||
export default ({ loadTestFile }: FtrProviderContext) => {
|
||||
describe('Triggers Actions UI Example', function () {
|
||||
loadTestFile(require.resolve('./rule_status_dropdown'));
|
||||
loadTestFile(require.resolve('./rule_tag_filter'));
|
||||
|
@ -16,5 +16,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => {
|
|||
loadTestFile(require.resolve('./rule_event_log_list'));
|
||||
loadTestFile(require.resolve('./rules_list'));
|
||||
loadTestFile(require.resolve('./alerts_table'));
|
||||
loadTestFile(require.resolve('./rules_settings_link'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
|
||||
});
|
||||
|
||||
it('shoud load from shareable lazy loader', async () => {
|
||||
it('should load from shareable lazy loader', async () => {
|
||||
await testSubjects.find('ruleTagFilter');
|
||||
const exists = await testSubjects.exists('ruleTagFilter');
|
||||
expect(exists).to.be(true);
|
||||
|
|
|
@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
|
||||
});
|
||||
|
||||
it('shoud load from shareable lazy loader', async () => {
|
||||
it('should load from shareable lazy loader', async () => {
|
||||
await testSubjects.find('rulesList');
|
||||
const exists = await testSubjects.exists('rulesList');
|
||||
expect(exists).to.be(true);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../../test/functional/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
|
||||
describe('Rules Settings Link', () => {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('triggersActionsUiExample/rules_settings_link');
|
||||
});
|
||||
|
||||
it('should load from shareable lazy loader', async () => {
|
||||
const exists = await testSubjects.exists('rulesSettingsLink');
|
||||
expect(exists).to.be(true);
|
||||
});
|
||||
|
||||
it('should be able to open the modal', async () => {
|
||||
await testSubjects.click('rulesSettingsLink');
|
||||
await testSubjects.waitForDeleted('centerJustifiedSpinner');
|
||||
await testSubjects.existOrFail('rulesSettingsModal');
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue