diff --git a/frontend/src/component/application/Application.tsx b/frontend/src/component/application/Application.tsx index df96180635..bd1e621ed9 100644 --- a/frontend/src/component/application/Application.tsx +++ b/frontend/src/component/application/Application.tsx @@ -129,7 +129,7 @@ export const Application = () => { name: 'overview', }, { - title: 'Connected instances', + title: 'Last Seen Instances (24h)', path: `${basePath}/instances`, name: 'instances', }, diff --git a/frontend/src/component/application/ApplicationChart.tsx b/frontend/src/component/application/ApplicationChart.tsx index c76a7ea64b..10707a664f 100644 --- a/frontend/src/component/application/ApplicationChart.tsx +++ b/frontend/src/component/application/ApplicationChart.tsx @@ -287,7 +287,7 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => { theme.fontSizes .smallBody } - tooltip='Active instances in the last 2 days' + tooltip='Active instances in the last 24 hours' /> diff --git a/src/lib/db/client-applications-store.ts b/src/lib/db/client-applications-store.ts index 4668357def..8a59a235df 100644 --- a/src/lib/db/client-applications-store.ts +++ b/src/lib/db/client-applications-store.ts @@ -331,6 +331,7 @@ export default class ClientApplicationsStore ]) .from('client_instances as ci') .where('ci.app_name', appName) + .whereRaw("ci.last_seen >= NOW() - INTERVAL '24 hours'") .groupBy('ci.app_name', 'ci.environment'); }) .select([ @@ -378,7 +379,7 @@ export default class ClientApplicationsStore if (!environment) return acc; - strategies.forEach((strategy) => { + strategies?.forEach((strategy) => { if ( !DEPRECATED_STRATEGIES.includes(strategy) && !existingStrategies.includes(strategy) diff --git a/src/lib/db/client-instance-store.ts b/src/lib/db/client-instance-store.ts index 0caf4111ea..b2c76cf968 100644 --- a/src/lib/db/client-instance-store.ts +++ b/src/lib/db/client-instance-store.ts @@ -180,7 +180,7 @@ export default class ClientInstanceStore implements IClientInstanceStore { return rows.map(mapRow); } - async getByAppNameAndEnvironment( + async getRecentByAppNameAndEnvironment( appName: string, environment: string, ): Promise { @@ -189,6 +189,7 @@ export default class ClientInstanceStore implements IClientInstanceStore { .from(TABLE) .where('app_name', appName) .where('environment', environment) + .whereRaw("last_seen >= NOW() - INTERVAL '24 hours'") .orderBy('last_seen', 'desc') .limit(1000); diff --git a/src/lib/features/metrics/instance/instance-service.ts b/src/lib/features/metrics/instance/instance-service.ts index c2ec17d3ea..541de3dacb 100644 --- a/src/lib/features/metrics/instance/instance-service.ts +++ b/src/lib/features/metrics/instance/instance-service.ts @@ -262,12 +262,12 @@ export default class ClientInstanceService { return result; } - async getApplicationEnvironmentInstances( + async getRecentApplicationEnvironmentInstances( appName: string, environment: string, ) { const instances = - await this.clientInstanceStore.getByAppNameAndEnvironment( + await this.clientInstanceStore.getRecentByAppNameAndEnvironment( appName, environment, ); diff --git a/src/lib/routes/admin-api/metrics.ts b/src/lib/routes/admin-api/metrics.ts index 868febcd75..a6f0e51956 100644 --- a/src/lib/routes/admin-api/metrics.ts +++ b/src/lib/routes/admin-api/metrics.ts @@ -168,9 +168,9 @@ class MetricsController extends Controller { openApiService.validPath({ tags: ['Metrics'], operationId: 'getApplicationEnvironmentInstances', - summary: 'Get application environment instances', + summary: 'Get application environment instances (Last 24h)', description: - 'Returns an overview of the instances for the given `appName` and `environment` that receive traffic.', + 'Returns an overview of the instances for the given `appName` and `environment` that have received traffic in the last 24 hours.', responses: { 200: createResponseSchema( 'applicationEnvironmentInstancesSchema', @@ -315,7 +315,7 @@ class MetricsController extends Controller { ): Promise { const { appName, environment } = req.params; const instances = - await this.clientInstanceService.getApplicationEnvironmentInstances( + await this.clientInstanceService.getRecentApplicationEnvironmentInstances( appName, environment, ); diff --git a/src/lib/types/stores/client-instance-store.ts b/src/lib/types/stores/client-instance-store.ts index 1db8baeda2..0c12e046f6 100644 --- a/src/lib/types/stores/client-instance-store.ts +++ b/src/lib/types/stores/client-instance-store.ts @@ -21,7 +21,7 @@ export interface IClientInstanceStore setLastSeen(INewClientInstance): Promise; insert(details: INewClientInstance): Promise; getByAppName(appName: string): Promise; - getByAppNameAndEnvironment( + getRecentByAppNameAndEnvironment( appName: string, environment: string, ): Promise; diff --git a/src/test/e2e/api/admin/applications.e2e.test.ts b/src/test/e2e/api/admin/applications.e2e.test.ts index fc5bca8b86..4e484489b3 100644 --- a/src/test/e2e/api/admin/applications.e2e.test.ts +++ b/src/test/e2e/api/admin/applications.e2e.test.ts @@ -221,3 +221,50 @@ test('should show missing features and strategies', async () => { expect(body).toMatchObject(expected); }); + +test('should not return instances older than 24h', async () => { + await app.request + .post('/api/client/metrics') + .set('Authorization', defaultToken.secret) + .send(metrics) + .expect(202); + + await app.services.clientMetricsServiceV2.bulkAdd(); + + await db.stores.clientApplicationsStore.upsert({ + appName: metrics.appName, + }); + await db.stores.clientInstanceStore.insert({ + appName: metrics.appName, + clientIp: '127.0.0.1', + instanceId: 'old-instance', + lastSeen: new Date(Date.now() - 26 * 60 * 60 * 1000), // 26 hours ago + }); + + const { body } = await app.request + .get(`/api/admin/metrics/applications/${metrics.appName}/overview`) + .expect(200); + + const expected = { + environments: [ + { + instanceCount: 1, + }, + ], + }; + + expect(body).toMatchObject(expected); + + const { body: instancesBody } = await app.request + .get( + `/api/admin/metrics/instances/${metrics.appName}/environment/default`, + ) + .expect(200); + + expect(instancesBody.instances).toHaveLength(1); + expect(instancesBody.instances).toMatchObject([ + { + instanceId: metrics.instanceId, + }, + ]); +}); diff --git a/src/test/fixtures/fake-client-instance-store.ts b/src/test/fixtures/fake-client-instance-store.ts index 5201389efd..148a7a3a75 100644 --- a/src/test/fixtures/fake-client-instance-store.ts +++ b/src/test/fixtures/fake-client-instance-store.ts @@ -93,7 +93,7 @@ export default class FakeClientInstanceStore implements IClientInstanceStore { return this.instances.filter((i) => i.appName === appName); } - async getByAppNameAndEnvironment( + async getRecentByAppNameAndEnvironment( appName: string, environment: string, ): Promise {