diff --git a/frontend/src/component/integrations/IntegrationEventsModal/IntegrationEventsModal.tsx b/frontend/src/component/integrations/IntegrationEventsModal/IntegrationEventsModal.tsx
index 00ec13da96..55cdf7cc4a 100644
--- a/frontend/src/component/integrations/IntegrationEventsModal/IntegrationEventsModal.tsx
+++ b/frontend/src/component/integrations/IntegrationEventsModal/IntegrationEventsModal.tsx
@@ -105,8 +105,10 @@ export const IntegrationEventsModal = ({
- All events older than the last 100 or older than the
- past 2 hours will be automatically deleted.
+ Only the most recent event for each integration and
+ the last 100 events from the past 2 hours will be
+ kept. All other events will be automatically
+ deleted.
diff --git a/src/lib/features/integration-events/integration-events-store.ts b/src/lib/features/integration-events/integration-events-store.ts
index 553e19897e..796de267f5 100644
--- a/src/lib/features/integration-events/integration-events-store.ts
+++ b/src/lib/features/integration-events/integration-events-store.ts
@@ -26,26 +26,45 @@ export class IntegrationEventsStore extends CRUDStore<
limit: number,
offset: number,
): Promise {
+ const endTimer = this.timer('getPaginatedEvents');
+
const rows = await this.db(this.tableName)
.where('integration_id', id)
.limit(limit)
.offset(offset)
.orderBy('id', 'desc');
+ endTimer();
+
return rows.map(this.fromRow) as IntegrationEventSchema[];
}
async cleanUpEvents(): Promise {
- return this.db
+ const endTimer = this.timer('cleanUpEvents');
+
+ await this.db
.with('latest_events', (qb) => {
qb.select('id')
.from(this.tableName)
.whereRaw(`created_at >= now() - INTERVAL '2 hours'`)
- .orderBy('created_at', 'desc')
+ .orderBy('id', 'desc')
.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)
- .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();
+
+ endTimer();
}
}
diff --git a/src/lib/features/integration-events/integration-events.e2e.test.ts b/src/lib/features/integration-events/integration-events.e2e.test.ts
index 136cf78e24..cd1b26f218 100644
--- a/src/lib/features/integration-events/integration-events.e2e.test.ts
+++ b/src/lib/features/integration-events/integration-events.e2e.test.ts
@@ -5,6 +5,7 @@ import {
} from '../../../test/e2e/helpers/test-helper';
import getLogger from '../../../test/fixtures/no-logger';
import { TEST_AUDIT_USER } from '../../types';
+import type { IAddonDto } from '../../types/stores/addon-store';
import type { IntegrationEventsService } from './integration-events-service';
import type { IntegrationEventWriteModel } from './integration-events-store';
@@ -35,6 +36,15 @@ const EVENT_FAILED: IntegrationEventWriteModel = {
stateDetails: 'Better Call Saul!',
};
+const INTEGRATION: IAddonDto = {
+ provider: 'webhook',
+ enabled: true,
+ parameters: {
+ url: 'http://some-test-url',
+ },
+ events: ['feature-created'],
+};
+
beforeAll(async () => {
db = await dbInit('integration_events', getLogger);
app = await setupAppWithCustomConfig(
@@ -55,14 +65,7 @@ beforeEach(async () => {
await db.reset();
const { id } = await app.services.addonService.createAddon(
- {
- provider: 'webhook',
- enabled: true,
- parameters: {
- url: 'http://some-test-url',
- },
- events: ['feature-created'],
- },
+ INTEGRATION,
TEST_AUDIT_USER,
);
@@ -193,6 +196,45 @@ test('clean up events, keeping the last 100 events', async () => {
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 () => {
await integrationEventsService.registerEvent(getTestEventSuccess());
await integrationEventsService.registerEvent(getTestEventFailed());