[ResponseOps] Hide the query delay capability from non-serverless projects for now (#169965)

Resolves https://github.com/elastic/kibana/issues/169868

## Summary

Hides the query delay modal and sub feature privilege for non-serverless
projects until we onboard all rule types


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### To verify

- Run a non serverless project and verify that you can't edit the query
delay settings in the rules settings modal
- Run a serverless project for search and observability to verify that
you can still edit and update the query delay settings

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Alexi Doak 2023-11-03 07:10:15 -07:00 committed by GitHub
parent 74509cdc33
commit 8a1ad098c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 329 additions and 96 deletions

View file

@ -97,7 +97,7 @@ import {
type InitializationPromise,
errorResult,
} from './alerts_service';
import { rulesSettingsFeature } from './rules_settings_feature';
import { getRulesSettingsFeature } from './rules_settings_feature';
import { maintenanceWindowFeature } from './maintenance_window_feature';
import { DataStreamAdapter, getDataStreamAdapter } from './alerts_service/lib/data_stream_adapter';
import { createGetAlertIndicesAliasFn, GetAlertIndicesAlias } from './lib';
@ -255,7 +255,7 @@ export class AlertingPlugin {
};
});
plugins.features.registerKibanaFeature(rulesSettingsFeature);
plugins.features.registerKibanaFeature(getRulesSettingsFeature(!!plugins.serverless));
plugins.features.registerKibanaFeature(maintenanceWindowFeature);

View file

@ -0,0 +1,194 @@
/*
* 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 { getRulesSettingsFeature } from './rules_settings_feature';
test('returns rule settings feature with query delay subfeature if serverless', async () => {
expect(getRulesSettingsFeature(true)).toEqual({
app: [],
category: {
euiIconType: 'managementApp',
id: 'management',
label: 'Management',
order: 5000,
},
id: 'rulesSettings',
management: {
insightsAndAlerting: ['triggersActions'],
},
name: 'Rules Settings',
privileges: {
all: {
api: [],
app: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: ['rules-settings'],
read: [],
},
ui: ['show', 'save'],
},
read: {
api: [],
app: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: [],
read: ['rules-settings'],
},
ui: ['show'],
},
},
subFeatures: [
{
name: 'Flapping detection',
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
api: ['read-flapping-settings', 'write-flapping-settings'],
id: 'allFlappingSettings',
includeIn: 'all',
name: 'All',
savedObject: {
all: ['rules-settings'],
read: [],
},
ui: ['writeFlappingSettingsUI', 'readFlappingSettingsUI'],
},
{
api: ['read-flapping-settings'],
id: 'readFlappingSettings',
includeIn: 'read',
name: 'Read',
savedObject: {
all: [],
read: ['rules-settings'],
},
ui: ['readFlappingSettingsUI'],
},
],
},
],
},
{
name: 'Query delay',
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
api: ['read-query-delay-settings', 'write-query-delay-settings'],
id: 'allQueryDelaySettings',
includeIn: 'all',
name: 'All',
savedObject: {
all: ['rules-settings'],
read: [],
},
ui: ['writeQueryDelaySettingsUI', 'readQueryDelaySettingsUI'],
},
{
api: ['read-query-delay-settings'],
id: 'readQueryDelaySettings',
includeIn: 'read',
name: 'Read',
savedObject: {
all: [],
read: ['rules-settings'],
},
ui: ['readQueryDelaySettingsUI'],
},
],
},
],
},
],
});
});
test('returns rule settings feature without query delay subfeature if not serverless', async () => {
expect(getRulesSettingsFeature(false)).toEqual({
app: [],
category: {
euiIconType: 'managementApp',
id: 'management',
label: 'Management',
order: 5000,
},
id: 'rulesSettings',
management: {
insightsAndAlerting: ['triggersActions'],
},
name: 'Rules Settings',
privileges: {
all: {
api: [],
app: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: ['rules-settings'],
read: [],
},
ui: ['show', 'save'],
},
read: {
api: [],
app: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: [],
read: ['rules-settings'],
},
ui: ['show'],
},
},
subFeatures: [
{
name: 'Flapping detection',
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
api: ['read-flapping-settings', 'write-flapping-settings'],
id: 'allFlappingSettings',
includeIn: 'all',
name: 'All',
savedObject: {
all: ['rules-settings'],
read: [],
},
ui: ['writeFlappingSettingsUI', 'readFlappingSettingsUI'],
},
{
api: ['read-flapping-settings'],
id: 'readFlappingSettings',
includeIn: 'read',
name: 'Read',
savedObject: {
all: [],
read: ['rules-settings'],
},
ui: ['readFlappingSettingsUI'],
},
],
},
],
},
],
});
});

View file

@ -18,78 +18,86 @@ import {
READ_QUERY_DELAY_SETTINGS_SUB_FEATURE_ID,
} from '../common';
export const rulesSettingsFeature: KibanaFeatureConfig = {
id: RULES_SETTINGS_FEATURE_ID,
name: i18n.translate('xpack.alerting.feature.rulesSettingsFeatureName', {
defaultMessage: 'Rules Settings',
}),
category: DEFAULT_APP_CATEGORIES.management,
app: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
privileges: {
all: {
app: [],
api: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
read: [],
},
ui: ['show', 'save'],
export function getRulesSettingsFeature(isServerless: boolean): KibanaFeatureConfig {
const settings = {
id: RULES_SETTINGS_FEATURE_ID,
name: i18n.translate('xpack.alerting.feature.rulesSettingsFeatureName', {
defaultMessage: 'Rules Settings',
}),
category: DEFAULT_APP_CATEGORIES.management,
app: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
read: {
app: [],
api: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: [],
read: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
},
ui: ['show'],
},
},
subFeatures: [
{
name: i18n.translate('xpack.alerting.feature.flappingSettingsSubFeatureName', {
defaultMessage: 'Flapping detection',
}),
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
api: [API_PRIVILEGES.READ_FLAPPING_SETTINGS, API_PRIVILEGES.WRITE_FLAPPING_SETTINGS],
name: 'All',
id: ALL_FLAPPING_SETTINGS_SUB_FEATURE_ID,
includeIn: 'all',
savedObject: {
all: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
read: [],
},
ui: ['writeFlappingSettingsUI', 'readFlappingSettingsUI'],
},
{
api: [API_PRIVILEGES.READ_FLAPPING_SETTINGS],
name: 'Read',
id: READ_FLAPPING_SETTINGS_SUB_FEATURE_ID,
includeIn: 'read',
savedObject: {
all: [],
read: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
},
ui: ['readFlappingSettingsUI'],
},
],
privileges: {
all: {
app: [],
api: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
],
savedObject: {
all: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
read: [],
},
ui: ['show', 'save'],
},
read: {
app: [],
api: [],
management: {
insightsAndAlerting: ['triggersActions'],
},
savedObject: {
all: [],
read: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
},
ui: ['show'],
},
},
{
subFeatures: [
{
name: i18n.translate('xpack.alerting.feature.flappingSettingsSubFeatureName', {
defaultMessage: 'Flapping detection',
}),
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
api: [
API_PRIVILEGES.READ_FLAPPING_SETTINGS,
API_PRIVILEGES.WRITE_FLAPPING_SETTINGS,
],
name: 'All',
id: ALL_FLAPPING_SETTINGS_SUB_FEATURE_ID,
includeIn: 'all',
savedObject: {
all: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
read: [],
},
ui: ['writeFlappingSettingsUI', 'readFlappingSettingsUI'],
},
{
api: [API_PRIVILEGES.READ_FLAPPING_SETTINGS],
name: 'Read',
id: READ_FLAPPING_SETTINGS_SUB_FEATURE_ID,
includeIn: 'read',
savedObject: {
all: [],
read: [RULES_SETTINGS_SAVED_OBJECT_TYPE],
},
ui: ['readFlappingSettingsUI'],
},
],
},
],
},
],
};
if (isServerless) {
settings.subFeatures.push({
name: i18n.translate('xpack.alerting.feature.queryDelaySettingsSubFeatureName', {
defaultMessage: 'Query delay',
}),
@ -125,6 +133,8 @@ export const rulesSettingsFeature: KibanaFeatureConfig = {
],
},
],
},
],
};
});
}
return settings as KibanaFeatureConfig;
}

View file

@ -43,7 +43,8 @@
"licensing",
"usageCollection",
"cloud",
"spaces"
"spaces",
"serverless"
],
"requiredBundles": [
"data",

View file

@ -54,6 +54,7 @@ export const renderApp = ({
usageCollection,
isDev,
kibanaVersion,
isServerless,
}: {
core: CoreStart;
config: ConfigSchema;
@ -64,6 +65,7 @@ export const renderApp = ({
usageCollection: UsageCollectionSetup;
isDev?: boolean;
kibanaVersion: string;
isServerless?: boolean;
}) => {
const { element, history, theme$ } = appMountParameters;
const i18nCore = core.i18n;
@ -97,6 +99,7 @@ export const renderApp = ({
storage: new Storage(localStorage),
isDev,
kibanaVersion,
isServerless,
}}
>
<ObservabilityAIAssistantProvider value={plugins.observabilityAIAssistant}>

View file

@ -58,6 +58,7 @@ import {
} from '@kbn/triggers-actions-ui-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { ServerlessPluginStart } from '@kbn/serverless/public';
import { observabilityAppId, observabilityFeatureId } from '../common';
import {
ALERTS_PATH,
@ -141,6 +142,7 @@ export interface ObservabilityPublicPluginsStart {
home?: HomePublicPluginStart;
cloud?: CloudStart;
aiops: AiopsPluginStart;
serverless?: ServerlessPluginStart;
}
export type ObservabilityPublicStart = ReturnType<Plugin['start']>;
@ -253,6 +255,7 @@ export class Plugin
ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate,
plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry },
usageCollection: pluginsSetup.usageCollection,
isServerless: !!pluginsStart.serverless,
});
};

View file

@ -92,7 +92,8 @@
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-theme",
"@kbn/shared-ux-link-redirect-app",
"@kbn/core-chrome-browser"
"@kbn/core-chrome-browser",
"@kbn/serverless"
],
"exclude": [
"target/**/*"

View file

@ -30,7 +30,8 @@
"cloud",
"features",
"home",
"spaces"
"spaces",
"serverless",
],
"requiredBundles": [
"alerting",

View file

@ -72,6 +72,7 @@ export interface TriggersAndActionsUiServices extends CoreStart {
unifiedSearch: UnifiedSearchPublicPluginStart;
licensing: LicensingPluginStart;
expressions: ExpressionsStart;
isServerless: boolean;
}
export const renderApp = (deps: TriggersAndActionsUiServices) => {

View file

@ -140,6 +140,8 @@ describe('rules_settings_modal', () => {
addWarning: jest.fn(),
} as unknown as IToasts;
useKibanaMock().services.isServerless = true;
getFlappingSettingsMock.mockResolvedValue(mockFlappingSetting);
updateFlappingSettingsMock.mockResolvedValue(mockFlappingSetting);
getQueryDelaySettingsMock.mockResolvedValue(mockQueryDelaySetting);
@ -436,4 +438,11 @@ describe('rules_settings_modal', () => {
expect(result.queryByTestId('rulesSettingsQueryDelaySection')).toBe(null);
});
test('hides query delay settings when not serverless', async () => {
useKibanaMock().services.isServerless = false;
const result = render(<RulesSettingsModalWithProviders {...modalProps} />);
await waitForModalLoad({ queryDelaySection: false });
expect(result.queryByTestId('rulesSettingsQueryDelaySection')).not.toBeInTheDocument();
});
});

View file

@ -92,6 +92,7 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => {
const {
application: { capabilities },
isServerless,
} = useKibana().services;
const {
rulesSettings: {
@ -229,7 +230,7 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => {
hasError={hasFlappingError}
/>
)}
{queryDelaySettings && (
{isServerless && queryDelaySettings && (
<>
<EuiSpacer />
<RulesSettingsQueryDelaySection

View file

@ -74,6 +74,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => {
theme$: themeServiceMock.createTheme$(),
licensing: licensingPluginMock,
expressions: expressionsPluginMock.createStartContract(),
isServerless: false,
} as TriggersAndActionsUiServices;
};

View file

@ -27,6 +27,7 @@ import { triggersActionsRoute } from '@kbn/rule-data-utils';
import { DashboardStart } from '@kbn/dashboard-plugin/public';
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { ExpressionsStart } from '@kbn/expressions-plugin/public';
import { ServerlessPluginStart } from '@kbn/serverless/public';
import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar';
import { TypeRegistry } from './application/type_registry';
@ -165,6 +166,7 @@ interface PluginsStart {
expressions: ExpressionsStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
licensing: LicensingPluginStart;
serverless?: ServerlessPluginStart;
}
export class Plugin
@ -290,6 +292,7 @@ export class Plugin
kibanaFeatures,
licensing: pluginsStart.licensing,
expressions: pluginsStart.expressions,
isServerless: !!pluginsStart.serverless,
});
},
});

View file

@ -55,6 +55,7 @@
"@kbn/dashboard-plugin",
"@kbn/licensing-plugin",
"@kbn/expressions-plugin",
"@kbn/serverless",
],
"exclude": ["target/**/*"]
}

View file

@ -39,6 +39,8 @@ export default function getQueryDelaySettingsTests({ getService }: FtrProviderCo
case 'space_1_all at space2':
case 'space_1_all_with_restricted_fixture at space1':
case 'space_1_all_alerts_none_actions at space1':
case 'global_read at space1':
case 'space_1_all at space1':
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
@ -46,9 +48,7 @@ export default function getQueryDelaySettingsTests({ getService }: FtrProviderCo
statusCode: 403,
});
break;
case 'global_read at space1':
case 'superuser at space1':
case 'space_1_all at space1':
expect(response.statusCode).to.eql(200);
expect(response.body.delay).to.eql(DEFAULT_QUERY_DELAY_SETTINGS.delay);
expect(response.body.updated_by).to.be.a('string');

View file

@ -39,6 +39,7 @@ export default function updateQueryDelaySettingsTest({ getService }: FtrProvider
case 'space_1_all at space2':
case 'space_1_all_with_restricted_fixture at space1':
case 'space_1_all_alerts_none_actions at space1':
case 'space_1_all at space1':
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
@ -47,7 +48,6 @@ export default function updateQueryDelaySettingsTest({ getService }: FtrProvider
});
break;
case 'superuser at space1':
case 'space_1_all at space1':
expect(response.statusCode).to.eql(200);
expect(response.body.delay).to.eql(20);
expect(response.body.updated_by).to.eql(user.username);

View file

@ -105,8 +105,6 @@ export default function ({ getService }: FtrProviderContext) {
'minimal_read',
'allFlappingSettings',
'readFlappingSettings',
'allQueryDelaySettings',
'readQueryDelaySettings',
],
maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'],
guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'],

View file

@ -187,8 +187,6 @@ export default function ({ getService }: FtrProviderContext) {
'minimal_read',
'allFlappingSettings',
'readFlappingSettings',
'allQueryDelaySettings',
'readQueryDelaySettings',
],
maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'],
guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'],

View file

@ -90,19 +90,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.existOrFail('rulesSettingsFlappingEnableSwitch');
await testSubjects.existOrFail('lookBackWindowRangeInput');
await testSubjects.existOrFail('statusChangeThresholdRangeInput');
await testSubjects.existOrFail('queryDelayRangeInput');
await testSubjects.missingOrFail('queryDelayRangeInput');
const lookBackWindowInput = await testSubjects.find('lookBackWindowRangeInput');
const statusChangeThresholdInput = await testSubjects.find('statusChangeThresholdRangeInput');
const queryDelayInput = await testSubjects.find('queryDelayRangeInput');
const lookBackWindowValue = await lookBackWindowInput.getAttribute('value');
const statusChangeThresholdValue = await statusChangeThresholdInput.getAttribute('value');
const queryDelayValue = await queryDelayInput.getAttribute('value');
expect(lookBackWindowValue).to.eql('10');
expect(statusChangeThresholdValue).to.eql('10');
expect(queryDelayValue).to.eql('10');
// Enable query delay tests once feature flag is removed
// await testSubjects.existOrFail('queryDelayRangeInput');
// const queryDelayInput = await testSubjects.find('queryDelayRangeInput');
// const queryDelayValue = await queryDelayInput.getAttribute('value');
// expect(queryDelayValue).to.eql('10');
});
it('should allow the user to modify rules settings', async () => {
@ -111,19 +115,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await dragRangeInput('lookBackWindowRangeInput', 5, 'right');
await dragRangeInput('statusChangeThresholdRangeInput', 5, 'left');
await dragRangeInput('queryDelayRangeInput', 5, 'left');
let lookBackWindowInput = await testSubjects.find('lookBackWindowRangeInput');
let statusChangeThresholdInput = await testSubjects.find('statusChangeThresholdRangeInput');
let queryDelayInput = await testSubjects.find('queryDelayRangeInput');
let lookBackWindowValue = await lookBackWindowInput.getAttribute('value');
let statusChangeThresholdValue = await statusChangeThresholdInput.getAttribute('value');
let queryDelayValue = await queryDelayInput.getAttribute('value');
expect(lookBackWindowValue).to.eql('15');
expect(statusChangeThresholdValue).to.eql('5');
expect(queryDelayValue).to.eql('5');
// Enable query delay tests once feature flag is removed
// await dragRangeInput('queryDelayRangeInput', 5, 'left');
// let queryDelayInput = await testSubjects.find('queryDelayRangeInput');
// let queryDelayValue = await queryDelayInput.getAttribute('value');
// expect(queryDelayValue).to.eql('5');
await testSubjects.click('rulesSettingsFlappingEnableSwitch');
await testSubjects.existOrFail('rulesSettingsFlappingOffPrompt');
@ -143,15 +149,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
lookBackWindowInput = await testSubjects.find('lookBackWindowRangeInput');
statusChangeThresholdInput = await testSubjects.find('statusChangeThresholdRangeInput');
queryDelayInput = await testSubjects.find('queryDelayRangeInput');
lookBackWindowValue = await lookBackWindowInput.getAttribute('value');
statusChangeThresholdValue = await statusChangeThresholdInput.getAttribute('value');
queryDelayValue = await queryDelayInput.getAttribute('value');
expect(lookBackWindowValue).to.eql('15');
expect(statusChangeThresholdValue).to.eql('5');
expect(queryDelayValue).to.eql('5');
// Enable query delay tests once feature flag is removed
// queryDelayInput = await testSubjects.find('queryDelayRangeInput');
// queryDelayValue = await queryDelayInput.getAttribute('value');
// expect(queryDelayValue).to.eql('5');
});
});
};