import { randomUUID } from 'crypto'; import { gql } from 'graphql-tag'; import { type DataSource } from 'typeorm'; import { makeAdminPanelAPIRequestWithGuestRole } from 'test/integration/graphql/suites/admin-panel/utils/make-admin-panel-api-request-with-guest-role.util'; import { makeAdminPanelAPIRequest } from 'test/integration/twenty-config/utils/make-admin-panel-api-request.util'; import { SEED_APPLE_WORKSPACE_ID, SEED_YCOMBINATOR_WORKSPACE_ID, } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant'; // displayNames of the seeded workspaces (see seeder-workspaces.constant.ts). // They drive the ASC ordering and the searchTerm-by-workspace-name assertions. const APPLE_WORKSPACE_DISPLAY_NAME = 'Apple'; const YCOMBINATOR_WORKSPACE_DISPLAY_NAME = 'YCombinator'; const FIND_STATS = gql` query FindAdminApplicationRegistrationStats($id: String!) { findAdminApplicationRegistrationStats(id: $id) { activeInstalls suspendedInstalls mostInstalledVersion versionDistribution { version count } } } `; const FIND_INSTALLED_WORKSPACES = gql` query FindAdminApplicationRegistrationInstalledWorkspaces( $input: FindApplicationRegistrationInstalledWorkspacesInput! ) { findAdminApplicationRegistrationInstalledWorkspaces(input: $input) { totalCount hasMore workspaces { id displayName logo version } } } `; const FIND_ALL_REGISTRATIONS = gql` query FindAllApplicationRegistrations( $limit: Int $offset: Int $searchTerm: String $isPreInstalledOnly: Boolean ) { findAllApplicationRegistrations( limit: $limit offset: $offset searchTerm: $searchTerm isPreInstalledOnly: $isPreInstalledOnly ) { totalCount hasMore registrations { id name } } } `; const REGISTRATION_NAME = 'admin-panel-stats-integration-test-registration'; const DELETED_WORKSPACE_ONLY_VERSION = '9.9.9'; type SeededApplication = { id: string; workspaceId: string; version: string; }; describe('Admin panel application registration stats and installed workspaces (integration)', () => { let dataSource: DataSource; let applicationRegistrationId: string; let deletedWorkspaceId: string; const seededApplicationIds: string[] = []; const insertApplication = async ({ workspaceId, version, }: { workspaceId: string; version: string; }): Promise => { const id = randomUUID(); await dataSource.query( `INSERT INTO core."application" (id, "universalIdentifier", name, version, "sourcePath", "sourceType", "workspaceId", "applicationRegistrationId") VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [ id, randomUUID(), 'admin-panel-stats-integration-test-app', version, '', 'local', workspaceId, applicationRegistrationId, ], ); seededApplicationIds.push(id); return { id, workspaceId, version }; }; beforeAll(async () => { dataSource = global.testDataSource; applicationRegistrationId = randomUUID(); await dataSource.query( `INSERT INTO core."applicationRegistration" (id, "universalIdentifier", name, "oAuthClientId", "oAuthRedirectUris", "oAuthScopes", "sourceType") VALUES ($1, $2, $3, $4, $5, $6, $7)`, [ applicationRegistrationId, randomUUID(), REGISTRATION_NAME, randomUUID(), [], [], 'local', ], ); // Two installs on version 2.0.0 (one per seeded workspace) and one on // 1.0.0, so the distribution is deterministic and 2.0.0 is the most // installed version. await insertApplication({ workspaceId: SEED_APPLE_WORKSPACE_ID, version: '2.0.0', }); await insertApplication({ workspaceId: SEED_YCOMBINATOR_WORKSPACE_ID, version: '2.0.0', }); await insertApplication({ workspaceId: SEED_YCOMBINATOR_WORKSPACE_ID, version: '1.0.0', }); deletedWorkspaceId = randomUUID(); const [{ workspaceCustomApplicationId }] = await dataSource.query( `SELECT "workspaceCustomApplicationId" FROM core."workspace" WHERE id = $1`, [SEED_APPLE_WORKSPACE_ID], ); await dataSource.query( `INSERT INTO core."workspace" (id, "displayName", subdomain, "activationStatus", "workspaceCustomApplicationId", "deletedAt") VALUES ($1, $2, $3, $4, $5, now())`, [ deletedWorkspaceId, 'admin-panel-stats-deleted-workspace', `admin-panel-stats-deleted-${deletedWorkspaceId}`, 'PENDING_CREATION', workspaceCustomApplicationId, ], ); await insertApplication({ workspaceId: deletedWorkspaceId, version: DELETED_WORKSPACE_ONLY_VERSION, }); }); afterAll(async () => { if (seededApplicationIds.length > 0) { await dataSource.query( `DELETE FROM core."application" WHERE id = ANY($1)`, [seededApplicationIds], ); } await dataSource.query( `DELETE FROM core."applicationRegistration" WHERE id = $1`, [applicationRegistrationId], ); await dataSource.query(`DELETE FROM core."workspace" WHERE id = $1`, [ deletedWorkspaceId, ]); }); describe('findAdminApplicationRegistrationStats', () => { it('returns active installs, version distribution and the most installed version', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_STATS, variables: { id: applicationRegistrationId }, }); expect(response.body.errors).toBeUndefined(); const stats = response.body.data?.findAdminApplicationRegistrationStats; expect(stats).toBeDefined(); expect(stats.activeInstalls).toBe(3); expect(stats.suspendedInstalls).toBe(0); expect(stats.mostInstalledVersion).toBe('2.0.0'); const distributionByVersion = Object.fromEntries( stats.versionDistribution.map( (entry: { version: string; count: number }) => [ entry.version, entry.count, ], ), ); expect(distributionByVersion).toEqual({ '2.0.0': 2, '1.0.0': 1 }); // Distribution is ordered by count DESC, so the top entry matches // mostInstalledVersion. expect(stats.versionDistribution[0].version).toBe('2.0.0'); }); it('excludes installs on soft-deleted workspaces', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_STATS, variables: { id: applicationRegistrationId }, }); expect(response.body.errors).toBeUndefined(); const stats = response.body.data?.findAdminApplicationRegistrationStats; expect(stats.activeInstalls).toBe(3); expect( stats.versionDistribution.map( (entry: { version: string }) => entry.version, ), ).not.toContain(DELETED_WORKSPACE_ONLY_VERSION); }); it('rejects a caller without the SECURITY permission flag', async () => { const response = await makeAdminPanelAPIRequestWithGuestRole({ query: FIND_STATS, variables: { id: applicationRegistrationId }, }); expect(response.body.errors).toBeDefined(); expect( response.body.data?.findAdminApplicationRegistrationStats, ).toBeFalsy(); }); }); describe('findAdminApplicationRegistrationInstalledWorkspaces', () => { it('returns installed workspaces ordered by display name with totalCount and hasMore', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_INSTALLED_WORKSPACES, variables: { input: { id: applicationRegistrationId, offset: 0 } }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAdminApplicationRegistrationInstalledWorkspaces; expect(result).toBeDefined(); expect(result.totalCount).toBe(3); expect(result.hasMore).toBe(false); expect(result.workspaces).toHaveLength(3); // Ordered by workspace.displayName ASC — Apple before YCombinator. expect(result.workspaces[0].displayName).toBe( APPLE_WORKSPACE_DISPLAY_NAME, ); expect(result.workspaces[0].version).toBe('2.0.0'); expect(result.workspaces[1].displayName).toBe( YCOMBINATOR_WORKSPACE_DISPLAY_NAME, ); expect(result.workspaces[2].displayName).toBe( YCOMBINATOR_WORKSPACE_DISPLAY_NAME, ); }); it('filters by workspace display name via searchTerm', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_INSTALLED_WORKSPACES, variables: { input: { id: applicationRegistrationId, offset: 0, searchTerm: APPLE_WORKSPACE_DISPLAY_NAME, }, }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAdminApplicationRegistrationInstalledWorkspaces; expect(result.totalCount).toBe(1); expect(result.hasMore).toBe(false); expect(result.workspaces).toHaveLength(1); expect(result.workspaces[0].displayName).toBe( APPLE_WORKSPACE_DISPLAY_NAME, ); }); it('returns an empty result instead of erroring when a search only matches installs on soft-deleted workspaces', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_INSTALLED_WORKSPACES, variables: { input: { id: applicationRegistrationId, offset: 0, searchTerm: DELETED_WORKSPACE_ONLY_VERSION, }, }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAdminApplicationRegistrationInstalledWorkspaces; expect(result.totalCount).toBe(0); expect(result.hasMore).toBe(false); expect(result.workspaces).toHaveLength(0); }); it('filters by application version via searchTerm', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_INSTALLED_WORKSPACES, variables: { input: { id: applicationRegistrationId, offset: 0, searchTerm: '1.0.0', }, }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAdminApplicationRegistrationInstalledWorkspaces; expect(result.totalCount).toBe(1); expect(result.workspaces).toHaveLength(1); expect(result.workspaces[0].version).toBe('1.0.0'); expect(result.workspaces[0].displayName).toBe( YCOMBINATOR_WORKSPACE_DISPLAY_NAME, ); }); it('rejects a caller without the SECURITY permission flag', async () => { const response = await makeAdminPanelAPIRequestWithGuestRole({ query: FIND_INSTALLED_WORKSPACES, variables: { input: { id: applicationRegistrationId, offset: 0 } }, }); expect(response.body.errors).toBeDefined(); expect( response.body.data?.findAdminApplicationRegistrationInstalledWorkspaces, ).toBeFalsy(); }); }); describe('findAllApplicationRegistrations', () => { it('filters registrations by name via searchTerm', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_ALL_REGISTRATIONS, variables: { limit: 25, offset: 0, searchTerm: REGISTRATION_NAME }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAllApplicationRegistrations; expect(result.totalCount).toBe(1); expect(result.hasMore).toBe(false); expect(result.registrations).toHaveLength(1); expect(result.registrations[0].id).toBe(applicationRegistrationId); expect(result.registrations[0].name).toBe(REGISTRATION_NAME); }); it('returns no registrations when searchTerm matches nothing', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_ALL_REGISTRATIONS, variables: { limit: 25, offset: 0, searchTerm: 'no-registration-matches-this-search-term', }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAllApplicationRegistrations; expect(result.totalCount).toBe(0); expect(result.registrations).toHaveLength(0); }); it('rejects a caller without the SECURITY permission flag', async () => { const response = await makeAdminPanelAPIRequestWithGuestRole({ query: FIND_ALL_REGISTRATIONS, variables: { limit: 25, offset: 0 }, }); expect(response.body.errors).toBeDefined(); expect(response.body.data?.findAllApplicationRegistrations).toBeFalsy(); }); }); describe('suspended workspace installs', () => { let suspendedWorkspaceId: string; let suspendedApplicationId: string; beforeAll(async () => { suspendedWorkspaceId = randomUUID(); const [{ workspaceCustomApplicationId }] = await dataSource.query( `SELECT "workspaceCustomApplicationId" FROM core."workspace" WHERE id = $1`, [SEED_APPLE_WORKSPACE_ID], ); await dataSource.query( `INSERT INTO core."workspace" (id, "displayName", subdomain, "activationStatus", "defaultRoleId", "databaseSchema", "workspaceCustomApplicationId") VALUES ($1, $2, $3, 'SUSPENDED', $4, $5, $6)`, [ suspendedWorkspaceId, 'admin-panel-stats-suspended-workspace', `admin-panel-stats-suspended-${suspendedWorkspaceId}`, randomUUID(), 'admin_panel_stats_suspended', workspaceCustomApplicationId, ], ); const { id } = await insertApplication({ workspaceId: suspendedWorkspaceId, version: '2.0.0', }); suspendedApplicationId = id; }); afterAll(async () => { await dataSource.query(`DELETE FROM core."application" WHERE id = $1`, [ suspendedApplicationId, ]); await dataSource.query(`DELETE FROM core."workspace" WHERE id = $1`, [ suspendedWorkspaceId, ]); }); it('counts them as installs and reports them separately', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_STATS, variables: { id: applicationRegistrationId }, }); expect(response.body.errors).toBeUndefined(); const stats = response.body.data?.findAdminApplicationRegistrationStats; expect(stats.activeInstalls).toBe(4); expect(stats.suspendedInstalls).toBe(1); }); it('lists them alongside installs on active workspaces', async () => { const response = await makeAdminPanelAPIRequest({ query: FIND_INSTALLED_WORKSPACES, variables: { input: { id: applicationRegistrationId, offset: 0, searchTerm: 'admin-panel-stats-suspended-workspace', }, }, }); expect(response.body.errors).toBeUndefined(); const result = response.body.data?.findAdminApplicationRegistrationInstalledWorkspaces; expect(result.totalCount).toBe(1); expect(result.workspaces[0].id).toBe(suspendedWorkspaceId); }); }); });