Skip to content

Commit

Permalink
feat: support filtering on project and environment fields for events (#…
Browse files Browse the repository at this point in the history
…1801)

* feat: support filtering on project and environment fields for events

Co-authored-by: Nuno Góis <github@nunogois.com>
  • Loading branch information
Christopher Kolstad and nunogois committed Jul 12, 2022
1 parent 847119f commit e3c9eaa
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/lib/db/addon-store.ts
Expand Up @@ -115,6 +115,8 @@ export default class AddonStore implements IAddonStore {
description: row.description,
parameters: row.parameters,
events: row.events,
projects: row.projects || [],
environments: row.environments || [],
createdAt: row.created_at,
};
}
Expand All @@ -127,6 +129,8 @@ export default class AddonStore implements IAddonStore {
description: addon.description,
parameters: JSON.stringify(addon.parameters),
events: JSON.stringify(addon.events),
projects: JSON.stringify(addon.projects || []),
environments: JSON.stringify(addon.environments || []),
};
}
}
12 changes: 12 additions & 0 deletions src/lib/openapi/spec/addon-schema.ts
Expand Up @@ -32,6 +32,18 @@ export const addonSchema = {
type: 'string',
},
},
projects: {
type: 'array',
items: {
type: 'string',
},
},
environments: {
type: 'array',
items: {
type: 'string',
},
},
},
components: {},
} as const;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/services/addon-schema.ts
Expand Up @@ -12,5 +12,7 @@ export const addonSchema = joi
.pattern(joi.string(), [joi.string(), joi.number(), joi.boolean()])
.optional(),
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 });
276 changes: 274 additions & 2 deletions src/lib/services/addon-service.test.ts
Expand Up @@ -13,15 +13,17 @@ import createStores from '../../test/fixtures/store';
import AddonService from './addon-service';
import { IAddonDto } from '../types/stores/addon-store';
import SimpleAddon from './addon-service-test-simple-addon';
import { IAddonProviders } from '../addons';

const MASKED_VALUE = '*****';

const addonProvider = { simple: new SimpleAddon() };
let addonProvider: IAddonProviders;

function getSetup() {
const stores = createStores();
const tagTypeService = new TagTypeService(stores, { getLogger });

addonProvider = { simple: new SimpleAddon() };
return {
addonService: new AddonService(
stores,
Expand Down Expand Up @@ -49,7 +51,7 @@ test('should load addon configurations', async () => {
test('should load provider definitions', async () => {
const { addonService } = getSetup();

const providerDefinitions = await addonService.getProviderDefinitions();
const providerDefinitions = addonService.getProviderDefinitions();

const simple = providerDefinitions.find((p) => p.name === 'simple');

Expand Down Expand Up @@ -110,6 +112,276 @@ test('should trigger simple-addon eventHandler', async () => {
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 () => {
const { addonService } = getSetup();

Expand Down
12 changes: 12 additions & 0 deletions src/lib/services/addon-service.ts
Expand Up @@ -105,6 +105,18 @@ export default class AddonService {
this.fetchAddonConfigs().then((addonInstances) => {
addonInstances
.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])
.forEach((addon) =>
addonProviders[addon.provider].handleEvent(
Expand Down
2 changes: 2 additions & 0 deletions src/lib/types/stores/addon-store.ts
Expand Up @@ -5,6 +5,8 @@ export interface IAddonDto {
description: string;
enabled: boolean;
parameters: Record<string, unknown>;
projects?: string[];
environments?: string[];
events: string[];
}

Expand Down
@@ -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,
);
};

0 comments on commit e3c9eaa

Please sign in to comment.