1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-19 01:17:18 +02:00

feat(#4205): update potentially stale events (#4270)

This PR does **one** thing:
it changes the events for potentially stale to:
  - Only being emitted when potentially stale gets turned on
- In doing so, it also simplifies the event that's getting emitted,
removing the `data` property.
- The event is also renamed to better match the existing
`feature-stale-on` and `...-off` events.

The addon listening was broken out into a separate PR (#4279)

## Old description

This change lets all addons listen for events when features get marked
or unmarked as potentially stale.

### Discussion

#### All addons?

Should this be available to all addons? I can't see a reason why it
shouldn't be available to all addons, but I might be missing
something.

**Update**: spoke to a couple people. Can see no reason why this isn't
okay.

#### Should it be behind a flag?

The feature is still behind a flag, but the event type is not. Should
we gate the event being available until we actually emit the event?
That would require some more code, but could yield less potential
confusion.

Open to hearing your thoughts.
This commit is contained in:
Thomas Heartman 2023-07-19 15:20:18 +02:00 committed by GitHub
parent 60b9431b67
commit 4bca470543
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 39 deletions

View File

@ -381,9 +381,9 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
async updatePotentiallyStaleFeatures(
currentTime?: string,
): Promise<{ name: string; potentiallyStale: boolean }[]> {
): Promise<{ name: string; potentiallyStale: boolean; project: string }[]> {
const query = this.db.raw(
`SELECT name, potentially_stale, (? > (features.created_at + ((
`SELECT name, project, potentially_stale, (? > (features.created_at + ((
SELECT feature_types.lifetime_days
FROM feature_types
WHERE feature_types.id = features.type
@ -399,9 +399,10 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
(potentially_stale ?? false) !==
(current_staleness ?? false),
)
.map(({ current_staleness, name }) => ({
.map(({ current_staleness, name, project }) => ({
potentiallyStale: current_staleness ?? false,
name,
project,
}));
await this.db(TABLE)

View File

@ -1,15 +1,20 @@
import { IUnleashConfig, IUnleashStores } from '../types';
import {
FEATURE_POTENTIALLY_STALE_ON,
IEvent,
IUnleashConfig,
IUnleashStores,
} from '../types';
import { createTestConfig } from '../../test/config/test-config';
import FeatureToggleService from './feature-toggle-service';
import { AccessService } from './access-service';
import { IChangeRequestAccessReadModel } from 'lib/features/change-request-access-service/change-request-access-read-model';
import { ISegmentService } from 'lib/segments/segment-service-interface';
test('Should store events', async () => {
expect.assertions(4);
test('Should only store events for potentially stale on', async () => {
expect.assertions(2);
const featureUpdates = [
{ name: 'feature1', potentiallyStale: true },
{ name: 'feature2', potentiallyStale: false },
{ name: 'feature1', potentiallyStale: true, project: 'default' },
{ name: 'feature2', potentiallyStale: false, project: 'default' },
];
const config = createTestConfig();
@ -18,20 +23,19 @@ test('Should store events', async () => {
featureToggleStore: {
updatePotentiallyStaleFeatures: () => featureUpdates,
},
featureTagStore: {
getAllTagsForFeature: () => [],
},
eventStore: {
batchStore: ([event1, event2]) => {
// expect 'feature1'
expect(event1.data).toMatchObject({
name: 'feature1',
potentiallyStale: true,
batchStore: (events: IEvent[]) => {
expect(events.length).toBe(1);
const [event1] = events;
expect(event1).toMatchObject({
featureName: 'feature1',
project: 'default',
type: FEATURE_POTENTIALLY_STALE_ON,
});
expect(event1.preData).toBeUndefined();
// expect 'feature1'
expect(event2.data).toMatchObject({
name: 'feature2',
potentiallyStale: false,
});
expect(event2.preData).toBeUndefined();
},
},
} as unknown as IUnleashStores,

View File

@ -40,8 +40,8 @@ import {
SKIP_CHANGE_REQUEST,
Unsaved,
WeightType,
FEATURE_POTENTIALLY_STALE_UPDATED,
StrategiesOrderChangedEvent,
PotentiallyStaleOnEvent,
} from '../types';
import { Logger } from '../logger';
import BadDataError from '../error/bad-data-error';
@ -2077,15 +2077,19 @@ class FeatureToggleService {
if (this.flagResolver.isEnabled('emitPotentiallyStaleEvents')) {
if (potentiallyStaleFeatures.length > 0) {
return this.eventStore.batchStore(
potentiallyStaleFeatures.map(
({ name, potentiallyStale }) => ({
type: FEATURE_POTENTIALLY_STALE_UPDATED,
createdBy: 'unleash-system',
data: {
name,
potentiallyStale,
},
}),
await Promise.all(
potentiallyStaleFeatures
.filter((feature) => feature.potentiallyStale)
.map(
async ({ name, project }) =>
new PotentiallyStaleOnEvent({
featureName: name,
project,
tags: await this.tagStore.getAllTagsForFeature(
name,
),
}),
),
),
);
}

View File

@ -122,8 +122,8 @@ export const SERVICE_ACCOUNT_CREATED = 'service-account-created' as const;
export const SERVICE_ACCOUNT_UPDATED = 'service-account-updated' as const;
export const SERVICE_ACCOUNT_DELETED = 'service-account-deleted' as const;
export const FEATURE_POTENTIALLY_STALE_UPDATED =
'feature-potentially-stale-updated' as const;
export const FEATURE_POTENTIALLY_STALE_ON =
'feature-potentially-stale-on' as const;
export const IEventTypes = [
APPLICATION_CREATED,
@ -227,7 +227,7 @@ export const IEventTypes = [
SERVICE_ACCOUNT_CREATED,
SERVICE_ACCOUNT_DELETED,
SERVICE_ACCOUNT_UPDATED,
FEATURE_POTENTIALLY_STALE_UPDATED,
FEATURE_POTENTIALLY_STALE_ON,
] as const;
export type IEventType = typeof IEventTypes[number];
@ -953,3 +953,19 @@ export class ApiTokenUpdatedEvent extends BaseEvent {
this.project = eventData.apiToken.project;
}
}
export class PotentiallyStaleOnEvent extends BaseEvent {
readonly featureName: string;
readonly project: string;
constructor(eventData: {
featureName: string;
project: string;
tags: ITag[];
}) {
super(FEATURE_POTENTIALLY_STALE_ON, 'unleash-system', eventData.tags);
this.featureName = eventData.featureName;
this.project = eventData.project;
}
}

View File

@ -34,7 +34,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
}): Promise<number>;
updatePotentiallyStaleFeatures(
currentTime?: string,
): Promise<{ name: string; potentiallyStale: boolean }[]>;
): Promise<{ name: string; potentiallyStale: boolean; project: string }[]>;
isPotentiallyStale(featureName: string): Promise<boolean>;
/**

View File

@ -84,7 +84,7 @@ describe('potentially_stale marking', () => {
);
expect(markedToggles).toStrictEqual([
{ name: 'feature1', potentiallyStale: true },
{ name: 'feature1', potentiallyStale: true, project: 'default' },
]);
});
@ -123,6 +123,7 @@ describe('potentially_stale marking', () => {
expectedMarkedFeatures.map((name: string) => ({
name,
potentiallyStale: true,
project: 'default',
})),
),
);
@ -173,7 +174,7 @@ describe('potentially_stale marking', () => {
);
expect(markedToggles).toStrictEqual([
{ name: 'feature1', potentiallyStale: true },
{ name: 'feature1', potentiallyStale: true, project: 'default' },
]);
expect(
@ -207,7 +208,11 @@ describe('potentially_stale marking', () => {
);
expect(markedToggles).toStrictEqual([
{ name: 'feature1', potentiallyStale: true },
{
name: 'feature1',
potentiallyStale: true,
project: 'default',
},
]);
expect(
@ -221,7 +226,13 @@ describe('potentially_stale marking', () => {
expect(
await featureToggleStore.updatePotentiallyStaleFeatures(),
).toStrictEqual([{ name: 'feature1', potentiallyStale: false }]);
).toStrictEqual([
{
name: 'feature1',
potentiallyStale: false,
project: 'default',
},
]);
const potentiallyStale =
await featureToggleStore.isPotentiallyStale('feature1');

View File

@ -254,7 +254,7 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
}
updatePotentiallyStaleFeatures(): Promise<
{ name: string; potentiallyStale: boolean }[]
{ name: string; potentiallyStale: boolean; project: string }[]
> {
throw new Error('Method not implemented.');
}