Make rule type param validation required (#154257)

Resolves: #153755

This PR intends to make rule type param validation function required.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ersin Erdal 2023-04-20 19:23:47 +02:00 committed by GitHub
parent 08a68dbe73
commit 00dfae4312
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 325 additions and 18 deletions

View file

@ -8,6 +8,7 @@
import { v4 as uuidv4 } from 'uuid';
import { range } from 'lodash';
import { RuleType } from '@kbn/alerting-plugin/server';
import { schema } from '@kbn/config-schema';
import {
DEFAULT_INSTANCES_TO_GENERATE,
ALERTING_EXAMPLE_APP_ID,
@ -78,4 +79,16 @@ export const alertType: RuleType<
};
},
producer: ALERTING_EXAMPLE_APP_ID,
validate: {
params: schema.object({
instances: schema.maybe(schema.number()),
thresholds: schema.maybe(
schema.object({
small: schema.maybe(schema.number()),
medium: schema.maybe(schema.number()),
large: schema.maybe(schema.number()),
})
),
}),
},
};

View file

@ -7,6 +7,7 @@
import axios from 'axios';
import { RuleType } from '@kbn/alerting-plugin/server';
import { schema } from '@kbn/config-schema';
import { Operator, Craft, ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
interface PeopleInSpace {
@ -84,4 +85,11 @@ export const alertType: RuleType<
getViewInAppRelativeUrl({ rule }) {
return `/app/${ALERTING_EXAMPLE_APP_ID}/astros/${rule.id}`;
},
validate: {
params: schema.object({
outerSpaceCapacity: schema.number(),
craft: schema.string(),
op: schema.string(),
}),
},
};

View file

@ -15,6 +15,7 @@ import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock';
import { getAlertsForNotification, processAlerts } from '../lib';
import { logAlerts } from '../task_runner/log_alerts';
import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings';
import { schema } from '@kbn/config-schema';
const scheduleActions = jest.fn();
const replaceState = jest.fn(() => ({ scheduleActions }));
@ -86,6 +87,9 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
producer: 'alerts',
cancelAlertsOnRuleTimeout: true,
ruleTaskTimeout: '5m',
validate: {
params: schema.any(),
},
};
const testAlert1 = {

View file

@ -24,6 +24,7 @@ import { v4 as uuidv4 } from 'uuid';
import { RecoveredActionGroup } from '../../common';
import { RegistryRuleType } from '../rule_type_registry';
import { AlertingAuthorizationFilterType } from './alerting_authorization_kuery';
import { schema } from '@kbn/config-schema';
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const features: jest.Mocked<FeaturesStartContract> = featuresPluginMock.createStart();
@ -196,6 +197,9 @@ beforeEach(() => {
return { state: {} };
},
producer: 'myApp',
validate: {
params: schema.any(),
},
}));
features.getKibanaFeatures.mockReturnValue([
myAppFeature,

View file

@ -27,6 +27,7 @@ import {
import { RuleRunMetrics } from '../rule_run_metrics_store';
import { EVENT_LOG_ACTIONS } from '../../plugin';
import { TaskRunnerTimerSpan } from '../../task_runner/task_runner_timer';
import { schema } from '@kbn/config-schema';
const mockNow = '2020-01-01T02:00:00.000Z';
const eventLogger = eventLoggerMock.create();
@ -42,6 +43,9 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
executor: jest.fn(),
producer: 'alerts',
ruleTaskTimeout: '1m',
validate: {
params: schema.any(),
},
};
const context: RuleContextOpts = {

View file

@ -8,6 +8,7 @@
import { createAlertEventLogRecordObject } from './create_alert_event_log_record_object';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
import { RecoveredActionGroup } from '../types';
import { schema } from '@kbn/config-schema';
const MAINTENANCE_WINDOW_IDS = ['test-1', 'test-2'];
@ -22,6 +23,9 @@ describe('createAlertEventLogRecordObject', () => {
recoveryActionGroup: RecoveredActionGroup,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
};
test('created alert event "execute-start"', async () => {

View file

@ -72,6 +72,9 @@ describe('getLicenseCheckForRuleType', () => {
minimumLicenseRequired: 'gold',
isExportable: true,
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
validate: {
params: { validate: (params) => params },
},
};
beforeEach(() => {
@ -207,6 +210,9 @@ describe('ensureLicenseForRuleType()', () => {
minimumLicenseRequired: 'gold',
isExportable: true,
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
validate: {
params: { validate: (params) => params },
},
};
beforeEach(() => {

View file

@ -17,7 +17,7 @@ export function validateRuleTypeParams<Params extends RuleTypeParams>(
}
try {
return validator.validate(params);
return validator.validate(params as Params);
} catch (err) {
throw Boom.badRequest(`params invalid: ${err.message}`);
}

View file

@ -71,6 +71,9 @@ const sampleRuleType: RuleType<never, never, {}, never, never, 'default'> = {
async executor() {
return { state: {} };
},
validate: {
params: { validate: (params) => params },
},
};
describe('Alerting Plugin', () => {

View file

@ -15,6 +15,7 @@ import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
import { loggingSystemMock } from '@kbn/core/server/mocks';
import { inMemoryMetricsMock } from './monitoring/in_memory_metrics.mock';
import { alertsServiceMock } from './alerts_service/alerts_service.mock';
import { schema } from '@kbn/config-schema';
const logger = loggingSystemMock.create().get();
let mockedLicenseState: jest.Mocked<ILicenseState>;
@ -62,6 +63,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
});
expect(registry.has('foo')).toEqual(true);
});
@ -83,6 +87,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
@ -116,6 +123,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
@ -140,6 +150,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
@ -167,6 +180,9 @@ describe('Create Lifecycle', () => {
executor: jest.fn(),
producer: 'alerts',
defaultScheduleInterval: 'foobar',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
@ -193,6 +209,9 @@ describe('Create Lifecycle', () => {
executor: jest.fn(),
producer: 'alerts',
defaultScheduleInterval: '10s',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(ruleType);
@ -219,6 +238,9 @@ describe('Create Lifecycle', () => {
executor: jest.fn(),
producer: 'alerts',
defaultScheduleInterval: '10s',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry({
...ruleTypeRegistryParams,
@ -255,6 +277,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
@ -284,6 +309,9 @@ describe('Create Lifecycle', () => {
producer: 'alerts',
minimumLicenseRequired: 'basic',
isExportable: true,
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(ruleType);
@ -317,6 +345,9 @@ describe('Create Lifecycle', () => {
producer: 'alerts',
minimumLicenseRequired: 'basic',
isExportable: true,
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(ruleType);
@ -354,6 +385,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
@ -380,6 +414,9 @@ describe('Create Lifecycle', () => {
executor: jest.fn(),
producer: 'alerts',
ruleTaskTimeout: '20m',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(ruleType);
@ -412,6 +449,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(ruleType);
@ -435,6 +475,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
expect(() =>
registry.register({
@ -451,6 +494,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
})
).toThrowErrorMatchingInlineSnapshot(`"Rule type \\"test\\" is already registered."`);
});
@ -475,6 +521,9 @@ describe('Create Lifecycle', () => {
context: 'test',
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
},
validate: {
params: { validate: (params) => params },
},
});
expect(alertsService.register).toHaveBeenCalledWith({
@ -499,6 +548,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
});
expect(alertsService.register).not.toHaveBeenCalled();
@ -522,6 +574,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
const ruleType = registry.get('test');
expect(ruleType).toMatchInlineSnapshot(`
@ -552,6 +607,11 @@ describe('Create Lifecycle', () => {
"id": "recovered",
"name": "Recovered",
},
"validate": Object {
"params": Object {
"validate": [Function],
},
},
}
`);
});
@ -589,6 +649,9 @@ describe('Create Lifecycle', () => {
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
});
const result = registry.list();
expect(result).toMatchInlineSnapshot(`
@ -689,6 +752,9 @@ describe('Create Lifecycle', () => {
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
});
const result = registry.getAllTypes();
expect(result).toEqual(['test']);
@ -715,6 +781,9 @@ describe('Create Lifecycle', () => {
isExportable: true,
minimumLicenseRequired: 'basic',
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
validate: {
params: schema.any(),
},
});
});
@ -750,6 +819,9 @@ function ruleTypeWithVariables<ActionGroupIds extends string>(
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
if (!context && !state) return baseAlert;

View file

@ -44,6 +44,9 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
cancelAlertsOnRuleTimeout: true,
ruleTaskTimeout: '5m',
getSummarizedAlerts: jest.fn(),
validate: {
params: { validate: (params) => params },
},
};
const context = {

View file

@ -26,6 +26,9 @@ describe('validateActions', () => {
cancelAlertsOnRuleTimeout: true,
ruleTaskTimeout: '5m',
getSummarizedAlerts: jest.fn(),
validate: {
params: { validate: (params) => params },
},
};
const data = {

View file

@ -503,11 +503,11 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleTypePara
}
// validate rule params
const validatedAlertTypeParams = validateRuleTypeParams(ruleParams, ruleType.validate?.params);
const validatedAlertTypeParams = validateRuleTypeParams(ruleParams, ruleType.validate.params);
const validatedMutatedAlertTypeParams = validateMutatedRuleTypeParams(
validatedAlertTypeParams,
rule.attributes.params,
ruleType.validate?.params
ruleType.validate.params
);
const {

View file

@ -88,7 +88,7 @@ export async function create<Params extends RuleTypeParams = never>(
// Throws an error if alert type isn't registered
const ruleType = context.ruleTypeRegistry.get(data.alertTypeId);
const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params);
const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate.params);
const username = await context.getUserName();
let createdAPIKey = null;

View file

@ -198,7 +198,7 @@ async function updateAlert<Params extends RuleTypeParams>(
}
// Validate
const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params);
const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate.params);
await validateActions(context, ruleType, data, allowMissingConnectorSecrets);
// Throw error if schedule interval is less than the minimum and we are enforcing it

View file

@ -27,6 +27,7 @@ import {
returnedRule2,
siemRule1,
} from './test_helpers';
import { schema } from '@kbn/config-schema';
import { migrateLegacyActions } from '../lib';
jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => {
@ -174,6 +175,9 @@ describe('bulkDelete', () => {
return { state: {} };
},
producer: 'alerts',
validate: {
params: schema.any(),
},
});
});

View file

@ -210,6 +210,9 @@ describe('bulkEdit()', () => {
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
(migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock);
@ -686,6 +689,9 @@ describe('bulkEdit()', () => {
},
producer: 'alerts',
getSummarizedAlerts: jest.fn().mockResolvedValue({}),
validate: {
params: { validate: (params) => params },
},
});
const existingAction = {
frequency: {

View file

@ -1233,6 +1233,9 @@ describe('create()', () => {
extractReferences: extractReferencesFn,
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
params: ruleParams,
@ -1414,6 +1417,9 @@ describe('create()', () => {
extractReferences: extractReferencesFn,
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
params: ruleParams,
@ -2679,6 +2685,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const createdAttributes = {
...data,
@ -2747,6 +2756,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({ schedule: { interval: '1s' } });
@ -2781,6 +2793,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
@ -2870,6 +2885,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
@ -2916,6 +2934,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
@ -2975,6 +2996,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
@ -3052,6 +3076,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
@ -3243,6 +3270,9 @@ describe('create()', () => {
injectReferences: jest.fn(),
},
getSummarizedAlerts: jest.fn().mockResolvedValue({}),
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({
@ -3292,6 +3322,9 @@ describe('create()', () => {
extractReferences: jest.fn(),
injectReferences: jest.fn(),
},
validate: {
params: { validate: (params) => params },
},
}));
const data = getMockData({

View file

@ -19,6 +19,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { getBeforeSetup, setGlobalDate } from './lib';
import { RecoveredActionGroup } from '../../../common';
import { RegistryRuleType } from '../../rule_type_registry';
import { schema } from '@kbn/config-schema';
import { enabledRule1, enabledRule2, siemRule1, siemRule2 } from './test_helpers';
import { formatLegacyActions } from '../lib';
@ -370,6 +371,9 @@ describe('find()', () => {
return { state: {} };
},
producer: 'myApp',
validate: {
params: schema.any(),
},
}));
ruleTypeRegistry.get.mockImplementationOnce(() => ({
id: '123',
@ -387,6 +391,9 @@ describe('find()', () => {
extractReferences: jest.fn(),
injectReferences: injectReferencesFn,
},
validate: {
params: schema.any(),
},
}));
unsecuredSavedObjectsClient.find.mockResolvedValue({
total: 2,
@ -570,6 +577,9 @@ describe('find()', () => {
return { state: {} };
},
producer: 'myApp',
validate: {
params: schema.any(),
},
}));
ruleTypeRegistry.get.mockImplementationOnce(() => ({
id: '123',
@ -587,6 +597,9 @@ describe('find()', () => {
extractReferences: jest.fn(),
injectReferences: injectReferencesFn,
},
validate: {
params: schema.any(),
},
}));
unsecuredSavedObjectsClient.find.mockResolvedValue({
total: 2,

View file

@ -232,6 +232,9 @@ describe('get()', () => {
extractReferences: jest.fn(),
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
@ -355,6 +358,9 @@ describe('get()', () => {
extractReferences: jest.fn(),
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({

View file

@ -116,6 +116,9 @@ export function getBeforeSetup(
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
}));
rulesClientParams.getEventLogClient.mockResolvedValue(
eventLogClient ?? eventLogClientMock.create()

View file

@ -284,6 +284,9 @@ describe('resolve()', () => {
extractReferences: jest.fn(),
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({
@ -417,6 +420,9 @@ describe('resolve()', () => {
extractReferences: jest.fn(),
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({

View file

@ -171,6 +171,9 @@ describe('update()', () => {
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
(migrateLegacyActions as jest.Mock).mockResolvedValue({
hasLegacyActions: false,
@ -749,6 +752,9 @@ describe('update()', () => {
extractReferences: extractReferencesFn,
injectReferences: injectReferencesFn,
},
validate: {
params: { validate: (params) => params },
},
}));
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
@ -1643,6 +1649,9 @@ describe('update()', () => {
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: alertId,

View file

@ -359,6 +359,9 @@ beforeEach(() => {
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
}));
ruleTypeRegistry.get.mockReturnValue({
@ -373,6 +376,9 @@ beforeEach(() => {
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
rulesClient = new RulesClient(rulesClientParams);

View file

@ -55,6 +55,9 @@ describe('isRuleExportable', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
expect(
isRuleExportable(
@ -111,6 +114,9 @@ describe('isRuleExportable', () => {
isExportable: false,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
expect(
isRuleExportable(
@ -170,6 +176,9 @@ describe('isRuleExportable', () => {
isExportable: false,
executor: jest.fn(),
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
});
expect(
isRuleExportable(

View file

@ -32,6 +32,7 @@ import { AlertInstanceState, AlertInstanceContext } from '../../common';
import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server';
import sinon from 'sinon';
import { mockAAD } from './fixtures';
import { schema } from '@kbn/config-schema';
jest.mock('./inject_action_params', () => ({
injectActionParams: jest.fn(),
@ -70,6 +71,9 @@ const ruleType: NormalizedRuleType<
executor: jest.fn(),
producer: 'alerts',
getSummarizedAlerts: getSummarizedAlertsMock,
validate: {
params: schema.any(),
},
};
const rule = {
id: '1',

View file

@ -156,6 +156,9 @@ export const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
cancelAlertsOnRuleTimeout: true,
ruleTaskTimeout: '5m',
autoRecoverAlerts: true,
validate: {
params: { validate: (params) => params },
},
};
export const mockRunNowResponse = {

View file

@ -571,7 +571,7 @@ export class TaskRunner<
this.alertingEventLogger.start();
return await loadRule<Params>({
paramValidator: this.ruleType.validate?.params,
paramValidator: this.ruleType.validate.params,
ruleId,
spaceId,
context: this.context,
@ -715,7 +715,7 @@ export class TaskRunner<
schedule = asOk(
(
await loadRule<Params>({
paramValidator: this.ruleType.validate?.params,
paramValidator: this.ruleType.validate.params,
ruleId,
spaceId,
context: this.context,

View file

@ -32,6 +32,7 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { rulesSettingsClientMock } from '../rules_settings_client.mock';
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
import { schema } from '@kbn/config-schema';
const inMemoryMetrics = inMemoryMetricsMock.create();
const executionContext = executionContextServiceMock.createSetupContract();
@ -58,6 +59,9 @@ const ruleType: UntypedNormalizedRuleType = {
},
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
};
let fakeTimer: sinon.SinonFakeTimers;

View file

@ -137,7 +137,7 @@ export type ExecutorType<
) => Promise<{ state: State }>;
export interface RuleTypeParamsValidator<Params extends RuleTypeParams> {
validate: (object: unknown) => Params;
validate: (object: Partial<Params>) => Params;
validateMutatedParams?: (mutatedOject: Params, origObject?: Params) => Params;
}
@ -235,8 +235,8 @@ export interface RuleType<
> {
id: string;
name: string;
validate?: {
params?: RuleTypeParamsValidator<Params>;
validate: {
params: RuleTypeParamsValidator<Params>;
};
actionGroups: Array<ActionGroup<ActionGroupIds>>;
defaultActionGroupId: ActionGroup<ActionGroupIds>['id'];

View file

@ -103,6 +103,13 @@ export class BaseRule {
actionVariables: {
context: actionVariables,
},
// As there is "[key: string]: unknown;" in CommonAlertParams,
// we couldn't figure out a schema for validation and created a follow on issue:
// https://github.com/elastic/kibana/issues/153754
// Below validate function should be overwritten in each monitoring rule type
validate: {
params: { validate: (params) => params },
},
};
}

View file

@ -116,11 +116,11 @@ describe('ruleType', () => {
groupBy: 'all',
};
expect(ruleType.validate?.params?.validate(params)).toBeTruthy();
expect(ruleType.validate.params.validate(params)).toBeTruthy();
});
it('validator fails with invalid es query params - threshold', async () => {
const paramsSchema = ruleType.validate?.params;
const paramsSchema = ruleType.validate.params;
if (!paramsSchema) throw new Error('params validator not set');
const params: Partial<Writable<OnlyEsQueryRuleParams>> = {
@ -556,11 +556,11 @@ describe('ruleType', () => {
});
it('validator succeeds with valid search source params', async () => {
expect(ruleType.validate?.params?.validate(defaultParams)).toBeTruthy();
expect(ruleType.validate.params.validate(defaultParams)).toBeTruthy();
});
it('validator fails with invalid search source params - esQuery provided', async () => {
const paramsSchema = ruleType.validate?.params!;
const paramsSchema = ruleType.validate.params;
const params: Partial<Writable<EsQueryRuleParams>> = {
size: 100,
timeWindowSize: 5,

View file

@ -138,11 +138,11 @@ describe('ruleType', () => {
threshold: [0],
};
expect(ruleType.validate?.params?.validate(params)).toBeTruthy();
expect(ruleType.validate.params.validate(params)).toBeTruthy();
});
it('validator fails with invalid params', async () => {
const paramsSchema = ruleType.validate?.params;
const paramsSchema = ruleType.validate.params;
if (!paramsSchema) throw new Error('params validator not set');
const params: Partial<Writable<Params>> = {

View file

@ -195,6 +195,9 @@ function getCumulativeFiringAlertType() {
},
};
},
validate: {
params: schema.any(),
},
};
return result;
}
@ -531,6 +534,9 @@ function getPatternFiringAlertType() {
},
};
},
validate: {
params: paramsSchema,
},
};
return result;
}
@ -572,6 +578,9 @@ function getPatternSuccessOrFailureAlertType() {
},
};
},
validate: {
params: paramsSchema,
},
};
return result;
}
@ -646,6 +655,9 @@ function getPatternFiringAutoRecoverFalseAlertType() {
},
};
},
validate: {
params: paramsSchema,
},
};
return result;
}
@ -692,6 +704,9 @@ function getLongRunningPatternRuleType(cancelAlertsOnRuleTimeout: boolean = true
}
return { state: {} };
},
validate: {
params: paramsSchema,
},
};
return result;
}
@ -752,6 +767,9 @@ function getCancellableRuleType() {
return { state: {} };
},
validate: {
params: paramsSchema,
},
};
return result;
}
@ -851,6 +869,9 @@ export function defineAlertTypes(
async executor() {
return { state: {} };
},
validate: {
params: schema.any(),
},
};
const goldNoopAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = {
id: 'test.gold.noop',
@ -863,6 +884,9 @@ export function defineAlertTypes(
async executor() {
return { state: {} };
},
validate: {
params: schema.any(),
},
};
const onlyContextVariablesAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = {
id: 'test.onlyContextVariables',
@ -878,6 +902,9 @@ export function defineAlertTypes(
async executor() {
return { state: {} };
},
validate: {
params: schema.any(),
},
};
const onlyStateVariablesAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = {
id: 'test.onlyStateVariables',
@ -893,6 +920,9 @@ export function defineAlertTypes(
async executor() {
return { state: {} };
},
validate: {
params: schema.any(),
},
};
const throwAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = {
id: 'test.throw',
@ -910,6 +940,9 @@ export function defineAlertTypes(
async executor() {
throw new Error('this alert is intended to fail');
},
validate: {
params: schema.any(),
},
};
function getLongRunningRuleType() {
const paramsSchema = schema.object({
@ -935,6 +968,9 @@ export function defineAlertTypes(
await new Promise((resolve) => setTimeout(resolve, params.delay ?? 5000));
return { state: {} };
},
validate: {
params: schema.any(),
},
};
return result;
}
@ -953,7 +989,11 @@ export function defineAlertTypes(
return { state: {} };
},
producer: 'alertsFixture',
validate: {
params: schema.any(),
},
};
const multipleSearchesRuleType: RuleType<
{ numSearches: number; delay: string },
{},
@ -1006,6 +1046,9 @@ export function defineAlertTypes(
return { state: {} };
},
validate: {
params: schema.object({ numSearches: schema.number(), delay: schema.string() }),
},
};
alerting.registerType(getAlwaysFiringAlertType());

View file

@ -7,6 +7,7 @@
import { CoreSetup } from '@kbn/core/server';
import { RuleType } from '@kbn/alerting-plugin/server';
import { schema } from '@kbn/config-schema';
import { FixtureStartDeps, FixtureSetupDeps } from './plugin';
export function defineAlertTypes(
@ -25,6 +26,9 @@ export function defineAlertTypes(
async executor() {
return { state: {} };
},
validate: {
params: schema.any(),
},
};
const noopUnrestrictedAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = {
id: 'test.unrestricted-noop',
@ -37,6 +41,9 @@ export function defineAlertTypes(
async executor() {
return { state: {} };
},
validate: {
params: schema.any(),
},
};
alerting.registerType(noopRestrictedAlertType);
alerting.registerType(noopUnrestrictedAlertType);

View file

@ -82,6 +82,9 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
await coreStart.elasticsearch.client.asInternalUser.ping();
return { state: {} };
},
validate: {
params: { validate: (params) => params },
},
});
const router = core.http.createRouter();

View file

@ -6,7 +6,11 @@
*/
import { Plugin, CoreSetup } from '@kbn/core/server';
import { PluginSetupContract as AlertingSetup, RuleType } from '@kbn/alerting-plugin/server';
import {
PluginSetupContract as AlertingSetup,
RuleType,
RuleTypeParams,
} from '@kbn/alerting-plugin/server';
import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
// this plugin's dependendencies
@ -26,10 +30,17 @@ export const noopAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = {
return { state: {} };
},
producer: 'alerts',
validate: {
params: { validate: (params) => params },
},
};
interface AlwaysFiringParams extends RuleTypeParams {
instances: Array<{ id: string; state: any }>;
}
export const alwaysFiringAlertType: RuleType<
{ instances: Array<{ id: string; state: any }> },
AlwaysFiringParams,
never, // Only use if defining useSavedObjectReferences hook
{
globalStateValue: boolean;
@ -66,6 +77,9 @@ export const alwaysFiringAlertType: RuleType<
},
};
},
validate: {
params: { validate: (params) => params as AlwaysFiringParams },
},
};
export const failingAlertType: RuleType<never, never, never, never, never, 'default' | 'other'> = {
@ -84,6 +98,9 @@ export const failingAlertType: RuleType<never, never, never, never, never, 'defa
async executor() {
throw new Error('Failed to execute alert type');
},
validate: {
params: { validate: (params) => params },
},
};
export class AlertingFixturePlugin implements Plugin<void, void, AlertingExampleDeps> {