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

chore: keep latest integration events for each integration configuration (#7652)

https://linear.app/unleash/issue/2-2469/keep-the-latest-event-for-each-integration-configuration

This makes it so we keep the latest event for each integration
configuration, along with the previous logic of keeping the latest 100
events of the last 2 hours.

This should be a cheap nice-to-have, since now we can always know what
the latest integration event looked like for each integration
configuration. This will tie-in nicely with the next task of making the
latest integration event state visible in the integration card.

Also improved the clarity of the auto-deletion explanation in the modal.
This commit is contained in:
Nuno Góis 2024-07-24 13:52:57 +01:00 committed by GitHub
parent 3f76882465
commit 8a20ae999f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 76 additions and 13 deletions

View File

@ -105,8 +105,10 @@ export const IntegrationEventsModal = ({
</StyledHeaderRow> </StyledHeaderRow>
<StyledHeaderRow> <StyledHeaderRow>
<StyledSubtitle> <StyledSubtitle>
All events older than the last 100 or older than the Only the most recent event for each integration and
past 2 hours will be automatically deleted. the last 100 events from the past 2 hours will be
kept. All other events will be automatically
deleted.
</StyledSubtitle> </StyledSubtitle>
</StyledHeaderRow> </StyledHeaderRow>
</StyledHeader> </StyledHeader>

View File

@ -26,26 +26,45 @@ export class IntegrationEventsStore extends CRUDStore<
limit: number, limit: number,
offset: number, offset: number,
): Promise<IntegrationEventSchema[]> { ): Promise<IntegrationEventSchema[]> {
const endTimer = this.timer('getPaginatedEvents');
const rows = await this.db(this.tableName) const rows = await this.db(this.tableName)
.where('integration_id', id) .where('integration_id', id)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.orderBy('id', 'desc'); .orderBy('id', 'desc');
endTimer();
return rows.map(this.fromRow) as IntegrationEventSchema[]; return rows.map(this.fromRow) as IntegrationEventSchema[];
} }
async cleanUpEvents(): Promise<void> { async cleanUpEvents(): Promise<void> {
return this.db const endTimer = this.timer('cleanUpEvents');
await this.db
.with('latest_events', (qb) => { .with('latest_events', (qb) => {
qb.select('id') qb.select('id')
.from(this.tableName) .from(this.tableName)
.whereRaw(`created_at >= now() - INTERVAL '2 hours'`) .whereRaw(`created_at >= now() - INTERVAL '2 hours'`)
.orderBy('created_at', 'desc') .orderBy('id', 'desc')
.limit(100); .limit(100);
}) })
.with('latest_per_integration', (qb) => {
qb.select(this.db.raw('MAX(id) as id'))
.from(this.tableName)
.groupBy('integration_id');
})
.from(this.tableName) .from(this.tableName)
.whereNotIn('id', this.db.select('id').from('latest_events')) .whereNotIn(
'id',
this.db
.select('id')
.from('latest_events')
.union(this.db.select('id').from('latest_per_integration')),
)
.delete(); .delete();
endTimer();
} }
} }

View File

@ -5,6 +5,7 @@ import {
} from '../../../test/e2e/helpers/test-helper'; } from '../../../test/e2e/helpers/test-helper';
import getLogger from '../../../test/fixtures/no-logger'; import getLogger from '../../../test/fixtures/no-logger';
import { TEST_AUDIT_USER } from '../../types'; import { TEST_AUDIT_USER } from '../../types';
import type { IAddonDto } from '../../types/stores/addon-store';
import type { IntegrationEventsService } from './integration-events-service'; import type { IntegrationEventsService } from './integration-events-service';
import type { IntegrationEventWriteModel } from './integration-events-store'; import type { IntegrationEventWriteModel } from './integration-events-store';
@ -35,6 +36,15 @@ const EVENT_FAILED: IntegrationEventWriteModel = {
stateDetails: 'Better Call Saul!', stateDetails: 'Better Call Saul!',
}; };
const INTEGRATION: IAddonDto = {
provider: 'webhook',
enabled: true,
parameters: {
url: 'http://some-test-url',
},
events: ['feature-created'],
};
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('integration_events', getLogger); db = await dbInit('integration_events', getLogger);
app = await setupAppWithCustomConfig( app = await setupAppWithCustomConfig(
@ -55,14 +65,7 @@ beforeEach(async () => {
await db.reset(); await db.reset();
const { id } = await app.services.addonService.createAddon( const { id } = await app.services.addonService.createAddon(
{ INTEGRATION,
provider: 'webhook',
enabled: true,
parameters: {
url: 'http://some-test-url',
},
events: ['feature-created'],
},
TEST_AUDIT_USER, TEST_AUDIT_USER,
); );
@ -193,6 +196,45 @@ test('clean up events, keeping the last 100 events', async () => {
expect(events).toHaveLength(100); expect(events).toHaveLength(100);
}); });
test('clean up events, keeping the latest event for each integration', async () => {
const longTimeAgo = new Date('2000-01-01');
const { id: integrationId2 } = await app.services.addonService.createAddon(
INTEGRATION,
TEST_AUDIT_USER,
);
await insertPastEvent(getTestEventSuccess(), longTimeAgo);
await insertPastEvent(getTestEventFailed(), longTimeAgo);
await insertPastEvent(
{ ...getTestEventSuccess(), integrationId: integrationId2 },
longTimeAgo,
);
await insertPastEvent(
{ ...getTestEventFailed(), integrationId: integrationId2 },
longTimeAgo,
);
await integrationEventsService.cleanUpEvents();
const eventsIntegration1 =
await integrationEventsService.getPaginatedEvents(integrationId, 10, 0);
expect(eventsIntegration1).toHaveLength(1);
expect(eventsIntegration1[0].state).toBe('failed');
const eventsIntegration2 =
await integrationEventsService.getPaginatedEvents(
integrationId2,
10,
0,
);
expect(eventsIntegration2).toHaveLength(1);
expect(eventsIntegration2[0].state).toBe('failed');
});
test('return events from the API', async () => { test('return events from the API', async () => {
await integrationEventsService.registerEvent(getTestEventSuccess()); await integrationEventsService.registerEvent(getTestEventSuccess());
await integrationEventsService.registerEvent(getTestEventFailed()); await integrationEventsService.registerEvent(getTestEventFailed());