mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
feat: support filtering on project and environment fields for events (#1801)
* feat: support filtering on project and environment fields for events Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
parent
847119f964
commit
e3c9eaae3a
@ -115,6 +115,8 @@ export default class AddonStore implements IAddonStore {
|
|||||||
description: row.description,
|
description: row.description,
|
||||||
parameters: row.parameters,
|
parameters: row.parameters,
|
||||||
events: row.events,
|
events: row.events,
|
||||||
|
projects: row.projects || [],
|
||||||
|
environments: row.environments || [],
|
||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -127,6 +129,8 @@ export default class AddonStore implements IAddonStore {
|
|||||||
description: addon.description,
|
description: addon.description,
|
||||||
parameters: JSON.stringify(addon.parameters),
|
parameters: JSON.stringify(addon.parameters),
|
||||||
events: JSON.stringify(addon.events),
|
events: JSON.stringify(addon.events),
|
||||||
|
projects: JSON.stringify(addon.projects || []),
|
||||||
|
environments: JSON.stringify(addon.environments || []),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,18 @@ export const addonSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
projects: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
environments: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {},
|
components: {},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -12,5 +12,7 @@ export const addonSchema = joi
|
|||||||
.pattern(joi.string(), [joi.string(), joi.number(), joi.boolean()])
|
.pattern(joi.string(), [joi.string(), joi.number(), joi.boolean()])
|
||||||
.optional(),
|
.optional(),
|
||||||
events: joi.array().optional().items(joi.string()),
|
events: joi.array().optional().items(joi.string()),
|
||||||
|
projects: joi.array().optional().items(joi.string()),
|
||||||
|
environments: joi.array().optional().items(joi.string()),
|
||||||
})
|
})
|
||||||
.options({ allowUnknown: false, stripUnknown: true });
|
.options({ allowUnknown: false, stripUnknown: true });
|
||||||
|
@ -13,15 +13,17 @@ import createStores from '../../test/fixtures/store';
|
|||||||
import AddonService from './addon-service';
|
import AddonService from './addon-service';
|
||||||
import { IAddonDto } from '../types/stores/addon-store';
|
import { IAddonDto } from '../types/stores/addon-store';
|
||||||
import SimpleAddon from './addon-service-test-simple-addon';
|
import SimpleAddon from './addon-service-test-simple-addon';
|
||||||
|
import { IAddonProviders } from '../addons';
|
||||||
|
|
||||||
const MASKED_VALUE = '*****';
|
const MASKED_VALUE = '*****';
|
||||||
|
|
||||||
const addonProvider = { simple: new SimpleAddon() };
|
let addonProvider: IAddonProviders;
|
||||||
|
|
||||||
function getSetup() {
|
function getSetup() {
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const tagTypeService = new TagTypeService(stores, { getLogger });
|
const tagTypeService = new TagTypeService(stores, { getLogger });
|
||||||
|
|
||||||
|
addonProvider = { simple: new SimpleAddon() };
|
||||||
return {
|
return {
|
||||||
addonService: new AddonService(
|
addonService: new AddonService(
|
||||||
stores,
|
stores,
|
||||||
@ -49,7 +51,7 @@ test('should load addon configurations', async () => {
|
|||||||
test('should load provider definitions', async () => {
|
test('should load provider definitions', async () => {
|
||||||
const { addonService } = getSetup();
|
const { addonService } = getSetup();
|
||||||
|
|
||||||
const providerDefinitions = await addonService.getProviderDefinitions();
|
const providerDefinitions = addonService.getProviderDefinitions();
|
||||||
|
|
||||||
const simple = providerDefinitions.find((p) => p.name === 'simple');
|
const simple = providerDefinitions.find((p) => p.name === 'simple');
|
||||||
|
|
||||||
@ -110,6 +112,276 @@ test('should trigger simple-addon eventHandler', async () => {
|
|||||||
expect(events[0].event.data.name).toBe('some-toggle');
|
expect(events[0].event.data.name).toBe('some-toggle');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not trigger event handler if project of event is different from addon', async () => {
|
||||||
|
const { addonService, stores } = getSetup();
|
||||||
|
const config = {
|
||||||
|
provider: 'simple',
|
||||||
|
enabled: true,
|
||||||
|
events: [FEATURE_CREATED],
|
||||||
|
projects: ['someproject'],
|
||||||
|
description: '',
|
||||||
|
parameters: {
|
||||||
|
url: 'http://localhost:wh',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await addonService.createAddon(config, 'me@mail.com');
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: 'someotherproject',
|
||||||
|
data: {
|
||||||
|
name: 'some-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const simpleProvider = addonService.addonProviders.simple;
|
||||||
|
// @ts-expect-error
|
||||||
|
const events = simpleProvider.getEvents();
|
||||||
|
|
||||||
|
expect(events.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should trigger event handler if project for event is one of the desired projects for addon', async () => {
|
||||||
|
const { addonService, stores } = getSetup();
|
||||||
|
const desiredProject = 'desired';
|
||||||
|
const otherProject = 'other';
|
||||||
|
const config = {
|
||||||
|
provider: 'simple',
|
||||||
|
enabled: true,
|
||||||
|
events: [FEATURE_CREATED],
|
||||||
|
projects: [desiredProject],
|
||||||
|
description: '',
|
||||||
|
parameters: {
|
||||||
|
url: 'http://localhost:wh',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await addonService.createAddon(config, 'me@mail.com');
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProject,
|
||||||
|
data: {
|
||||||
|
name: 'some-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: otherProject,
|
||||||
|
data: {
|
||||||
|
name: 'other-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const simpleProvider = addonService.addonProviders.simple;
|
||||||
|
// @ts-expect-error
|
||||||
|
const events = simpleProvider.getEvents();
|
||||||
|
|
||||||
|
expect(events.length).toBe(1);
|
||||||
|
expect(events[0].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[0].event.data.name).toBe('some-toggle');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should trigger events for multiple projects if addon is setup to filter multiple projects', async () => {
|
||||||
|
const { addonService, stores } = getSetup();
|
||||||
|
const desiredProjects = ['desired', 'desired2'];
|
||||||
|
const otherProject = 'other';
|
||||||
|
const config = {
|
||||||
|
provider: 'simple',
|
||||||
|
enabled: true,
|
||||||
|
events: [FEATURE_CREATED],
|
||||||
|
projects: desiredProjects,
|
||||||
|
description: '',
|
||||||
|
parameters: {
|
||||||
|
url: 'http://localhost:wh',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await addonService.createAddon(config, 'me@mail.com');
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProjects[0],
|
||||||
|
data: {
|
||||||
|
name: 'some-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: otherProject,
|
||||||
|
data: {
|
||||||
|
name: 'other-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProjects[1],
|
||||||
|
data: {
|
||||||
|
name: 'third-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const simpleProvider = addonService.addonProviders.simple;
|
||||||
|
// @ts-expect-error
|
||||||
|
const events = simpleProvider.getEvents();
|
||||||
|
|
||||||
|
expect(events.length).toBe(2);
|
||||||
|
expect(events[0].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[0].event.data.name).toBe('some-toggle');
|
||||||
|
expect(events[1].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[1].event.data.name).toBe('third-toggle');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should filter events on environment if addon is setup to filter for it', async () => {
|
||||||
|
const { addonService, stores } = getSetup();
|
||||||
|
const desiredEnvironment = 'desired';
|
||||||
|
const otherEnvironment = 'other';
|
||||||
|
const config = {
|
||||||
|
provider: 'simple',
|
||||||
|
enabled: true,
|
||||||
|
events: [FEATURE_CREATED],
|
||||||
|
projects: [],
|
||||||
|
environments: [desiredEnvironment],
|
||||||
|
description: '',
|
||||||
|
parameters: {
|
||||||
|
url: 'http://localhost:wh',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await addonService.createAddon(config, 'me@mail.com');
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredEnvironment,
|
||||||
|
environment: desiredEnvironment,
|
||||||
|
data: {
|
||||||
|
name: 'some-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
environment: otherEnvironment,
|
||||||
|
data: {
|
||||||
|
name: 'other-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const simpleProvider = addonService.addonProviders.simple;
|
||||||
|
// @ts-expect-error
|
||||||
|
const events = simpleProvider.getEvents();
|
||||||
|
|
||||||
|
expect(events.length).toBe(1);
|
||||||
|
expect(events[0].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[0].event.data.name).toBe('some-toggle');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should support filtering by both project and environment', async () => {
|
||||||
|
const { addonService, stores } = getSetup();
|
||||||
|
const desiredProjects = ['desired1', 'desired2', 'desired3'];
|
||||||
|
const desiredEnvironments = ['env1', 'env2', 'env3'];
|
||||||
|
const config = {
|
||||||
|
provider: 'simple',
|
||||||
|
enabled: true,
|
||||||
|
events: [FEATURE_CREATED],
|
||||||
|
projects: desiredProjects,
|
||||||
|
environments: desiredEnvironments,
|
||||||
|
description: '',
|
||||||
|
parameters: {
|
||||||
|
url: 'http://localhost:wh',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expectedFeatureNames = [
|
||||||
|
'desired-toggle1',
|
||||||
|
'desired-toggle2',
|
||||||
|
'desired-toggle3',
|
||||||
|
];
|
||||||
|
await addonService.createAddon(config, 'me@mail.com');
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProjects[0],
|
||||||
|
environment: desiredEnvironments[0],
|
||||||
|
data: {
|
||||||
|
name: expectedFeatureNames[0],
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProjects[0],
|
||||||
|
environment: 'wrongenvironment',
|
||||||
|
data: {
|
||||||
|
name: 'other-toggle',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProjects[2],
|
||||||
|
environment: desiredEnvironments[1],
|
||||||
|
data: {
|
||||||
|
name: expectedFeatureNames[1],
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: desiredProjects[2],
|
||||||
|
environment: desiredEnvironments[2],
|
||||||
|
data: {
|
||||||
|
name: expectedFeatureNames[2],
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await stores.eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'some@user.com',
|
||||||
|
project: 'wrongproject',
|
||||||
|
environment: desiredEnvironments[0],
|
||||||
|
data: {
|
||||||
|
name: 'not-expected',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const simpleProvider = addonService.addonProviders.simple;
|
||||||
|
// @ts-expect-error
|
||||||
|
const events = simpleProvider.getEvents();
|
||||||
|
|
||||||
|
expect(events.length).toBe(3);
|
||||||
|
expect(events[0].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[0].event.data.name).toBe(expectedFeatureNames[0]);
|
||||||
|
expect(events[1].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[1].event.data.name).toBe(expectedFeatureNames[1]);
|
||||||
|
expect(events[2].event.type).toBe(FEATURE_CREATED);
|
||||||
|
expect(events[2].event.data.name).toBe(expectedFeatureNames[2]);
|
||||||
|
});
|
||||||
|
|
||||||
test('should create simple-addon config', async () => {
|
test('should create simple-addon config', async () => {
|
||||||
const { addonService } = getSetup();
|
const { addonService } = getSetup();
|
||||||
|
|
||||||
|
@ -105,6 +105,18 @@ export default class AddonService {
|
|||||||
this.fetchAddonConfigs().then((addonInstances) => {
|
this.fetchAddonConfigs().then((addonInstances) => {
|
||||||
addonInstances
|
addonInstances
|
||||||
.filter((addon) => addon.events.includes(eventName))
|
.filter((addon) => addon.events.includes(eventName))
|
||||||
|
.filter(
|
||||||
|
(addon) =>
|
||||||
|
!addon.projects ||
|
||||||
|
addon.projects.length == 0 ||
|
||||||
|
addon.projects.includes(event.project),
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(addon) =>
|
||||||
|
!addon.environments ||
|
||||||
|
addon.environments.length == 0 ||
|
||||||
|
addon.environments.includes(event.environment),
|
||||||
|
)
|
||||||
.filter((addon) => addonProviders[addon.provider])
|
.filter((addon) => addonProviders[addon.provider])
|
||||||
.forEach((addon) =>
|
.forEach((addon) =>
|
||||||
addonProviders[addon.provider].handleEvent(
|
addonProviders[addon.provider].handleEvent(
|
||||||
|
@ -5,6 +5,8 @@ export interface IAddonDto {
|
|||||||
description: string;
|
description: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
parameters: Record<string, unknown>;
|
parameters: Record<string, unknown>;
|
||||||
|
projects?: string[];
|
||||||
|
environments?: string[];
|
||||||
events: string[];
|
events: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
exports.up = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`ALTER TABLE addons
|
||||||
|
ADD COLUMN
|
||||||
|
projects jsonb DEFAULT '[]'::jsonb;
|
||||||
|
ALTER TABLE addons
|
||||||
|
ADD COLUMN environments jsonb DEFAULT '[]'::jsonb;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE addons DROP COLUMN projects;
|
||||||
|
ALTER TABLE addons DROP COLUMN environments;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
@ -97,6 +97,12 @@ Object {
|
|||||||
"enabled": Object {
|
"enabled": Object {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
},
|
},
|
||||||
|
"environments": Object {
|
||||||
|
"items": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
"events": Object {
|
"events": Object {
|
||||||
"items": Object {
|
"items": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -110,6 +116,12 @@ Object {
|
|||||||
"additionalProperties": true,
|
"additionalProperties": true,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"projects": Object {
|
||||||
|
"items": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
"provider": Object {
|
"provider": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user