diff --git a/components/gitpod-db/src/typeorm/workspace-db-impl.ts b/components/gitpod-db/src/typeorm/workspace-db-impl.ts index 0386444bab6ece..fc027d64708ca5 100644 --- a/components/gitpod-db/src/typeorm/workspace-db-impl.ts +++ b/components/gitpod-db/src/typeorm/workspace-db-impl.ts @@ -158,7 +158,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { .addOrderBy('GREATEST(ws.creationTime, wsi.creationTime, wsi.startedTime, wsi.stoppedTime)', 'DESC') .limit(options.limit || 10); if (options.searchString) { - qb.andWhere("ws.description LIKE :searchString", {searchString: `%${options.searchString}%`}); + qb.andWhere("ws.description LIKE :searchString", { searchString: `%${options.searchString}%` }); } if (!options.includeHeadless) { qb.andWhere("ws.type = 'regular'"); @@ -334,6 +334,15 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { return { total, rows }; } + public async getInstanceCount(type?: string): Promise { + const workspaceInstanceRepo = await this.getWorkspaceInstanceRepo(); + const queryBuilder = workspaceInstanceRepo.createQueryBuilder("wsi") + .leftJoinAndMapOne("wsi.workspace", DBWorkspace, "ws", "wsi.workspaceId = ws.id") + .where("ws.type = :type", { type: type ? type.toString() : "regular" }); // only regular workspaces by default + + return await queryBuilder.getCount(); + } + public async findRegularRunningInstances(userId?: string): Promise { const infos = await this.findRunningInstancesWithWorkspaces(undefined, userId); return infos.filter( @@ -578,7 +587,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { public async findSnapshotsByWorkspaceId(workspaceId: string): Promise { const snapshots = await this.getSnapshotRepo(); - return snapshots.find({where: {originalWorkspaceId: workspaceId}}); + return snapshots.find({ where: { originalWorkspaceId: workspaceId } }); } public async storePrebuiltWorkspace(pws: PrebuiltWorkspace): Promise { @@ -596,7 +605,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { } const repo = await this.getPrebuiltWorkspaceRepo(); return await repo.createQueryBuilder('pws') - .where('pws.cloneURL = :cloneURL AND pws.commit LIKE :commit', { cloneURL, commit: commit+'%' }) + .where('pws.cloneURL = :cloneURL AND pws.commit LIKE :commit', { cloneURL, commit: commit + '%' }) .orderBy('pws.creationTime', 'DESC') .innerJoinAndMapOne('pws.workspace', DBWorkspace, 'ws', "pws.buildWorkspaceId = ws.id and ws.contentDeletedTime = ''") .getOne(); @@ -737,7 +746,13 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { return { total, rows }; } + public async getWorkspaceCount(type?: String): Promise { + const workspaceRepo = await this.getWorkspaceRepo(); + const queryBuilder = workspaceRepo.createQueryBuilder("ws") + .where("ws.type = :type", { type: type ? type.toString() : "regular" }); // only regular workspaces by default + return await queryBuilder.getCount(); + } public async findAllWorkspaceAndInstances(offset: number, limit: number, orderBy: keyof WorkspaceAndInstance, orderDir: "ASC" | "DESC", query?: AdminGetWorkspacesQuery, searchTerm?: string): Promise<{ total: number, rows: WorkspaceAndInstance[] }> { let whereConditions = []; @@ -827,7 +842,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { const workspaceRepo = await this.getWorkspaceRepo(); const workspace = await workspaceRepo.findOne(id); if (!workspace) { - return; + return; } const instance = await this.findCurrentInstance(id); @@ -890,7 +905,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { }); } - async findPrebuildInfos(prebuildIds: string[]): Promise{ + async findPrebuildInfos(prebuildIds: string[]): Promise { const repo = await this.getPrebuildInfoRepo(); const query = repo.createQueryBuilder('pi'); @@ -899,7 +914,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { if (filteredIds.length === 0) { return []; } - query.andWhere(`pi.prebuildId in (${ filteredIds.map(id => `'${id}'`).join(", ") })`) + query.andWhere(`pi.prebuildId in (${filteredIds.map(id => `'${id}'`).join(", ")})`) const res = await query.getMany(); return res.map(r => r.info); diff --git a/components/gitpod-db/src/workspace-db.ts b/components/gitpod-db/src/workspace-db.ts index dd93e9d1345fcf..478513c26d88c3 100644 --- a/components/gitpod-db/src/workspace-db.ts +++ b/components/gitpod-db/src/workspace-db.ts @@ -34,7 +34,7 @@ export interface WorkspacePortsAuthData { workspace: WorkspaceAuthData; } -export type WorkspaceInstanceSession = Pick; +export type WorkspaceInstanceSession = Pick; export type WorkspaceSessionData = Pick; export interface WorkspaceInstanceSessionWithWorkspace { instance: WorkspaceInstanceSession; @@ -67,7 +67,7 @@ export interface WorkspaceDB { // Partial update: unconditional, single field updates. Enclose in a transaction if necessary updateLastHeartbeat(instanceId: string, userId: string, newHeartbeat: Date, wasClosed?: boolean): Promise; - getLastOwnerHeartbeatFor(instance: WorkspaceInstance): Promise<{ lastSeen:Date, wasClosed?: boolean} | undefined>; + getLastOwnerHeartbeatFor(instance: WorkspaceInstance): Promise<{ lastSeen: Date, wasClosed?: boolean } | undefined>; getWorkspaceUsers(workspaceId: string, minLastSeen: number): Promise; updateInstancePartial(instanceId: string, partial: DeepPartial): Promise; @@ -84,12 +84,15 @@ export interface WorkspaceDB { findAllWorkspaceAndInstances(offset: number, limit: number, orderBy: keyof WorkspaceAndInstance, orderDir: "ASC" | "DESC", query?: AdminGetWorkspacesQuery, searchTerm?: string): Promise<{ total: number, rows: WorkspaceAndInstance[] }>; findWorkspaceAndInstance(id: string): Promise; + getWorkspaceCount(type?: String): Promise; + getInstanceCount(type?: string): Promise + findAllWorkspaceInstances(offset: number, limit: number, orderBy: keyof WorkspaceInstance, orderDir: "ASC" | "DESC", ownerId?: string, minCreationTime?: Date, maxCreationTime?: Date, onlyRunning?: boolean, type?: WorkspaceType): Promise<{ total: number, rows: WorkspaceInstance[] }>; findRegularRunningInstances(userId?: string): Promise; findRunningInstancesWithWorkspaces(installation?: string, userId?: string, includeStopping?: boolean): Promise; - isWhitelisted(repositoryUrl : string): Promise; + isWhitelisted(repositoryUrl: string): Promise; getFeaturedRepositories(): Promise[]>; findSnapshotById(snapshotId: string): Promise; diff --git a/components/gitpod-protocol/src/installation-admin-protocol.ts b/components/gitpod-protocol/src/installation-admin-protocol.ts index 2a3bbdd84c0577..94eb746c5a1476 100644 --- a/components/gitpod-protocol/src/installation-admin-protocol.ts +++ b/components/gitpod-protocol/src/installation-admin-protocol.ts @@ -23,6 +23,13 @@ export interface InstallationAdmin { settings: InstallationAdminSettings; } +export interface Data { + installationAdmin: InstallationAdmin + totalUsers: number + totalWorkspaces: number + totalInstances: number +} + export namespace InstallationAdmin { export function createDefault(): InstallationAdmin { return { diff --git a/components/installation-telemetry/cmd/send.go b/components/installation-telemetry/cmd/send.go index 1d2e5a202d9ee4..db6c0f8a9964f3 100644 --- a/components/installation-telemetry/cmd/send.go +++ b/components/installation-telemetry/cmd/send.go @@ -31,7 +31,7 @@ var sendCmd = &cobra.Command{ return err } - if !data.Settings.SendTelemetry { + if !data.InstallationAdmin.Settings.SendTelemetry { log.Info("installation-telemetry is not permitted to send - exiting") return nil } @@ -51,10 +51,13 @@ var sendCmd = &cobra.Command{ }() telemetry := analytics.Track{ - UserId: data.ID, + UserId: data.InstallationAdmin.ID, Event: "Installation telemetry", Properties: analytics.NewProperties(). - Set("version", versionId), + Set("version", versionId). + Set("totalUsers", data.TotalUsers). + Set("totalWorkspaces", data.TotalWorkspaces). + Set("totalInstances", data.TotalInstances), } client.Enqueue(telemetry) diff --git a/components/installation-telemetry/pkg/server/installationAdmin.go b/components/installation-telemetry/pkg/server/installationAdmin.go index d80a4e55ff6a0b..55dabcc46c8dd8 100644 --- a/components/installation-telemetry/pkg/server/installationAdmin.go +++ b/components/installation-telemetry/pkg/server/installationAdmin.go @@ -17,12 +17,19 @@ type InstallationAdminSettings struct { SendTelemetry bool `json:"sendTelemetry"` } -type InstallationAdminData struct { +type Data struct { + InstallationAdmin InstallationAdmin `json:"installationAdmin"` + TotalUsers int64 `json:"totalUsers"` + TotalWorkspaces int64 `json:"totalWorkspaces"` + TotalInstances int64 `json:"totalInstances"` +} + +type InstallationAdmin struct { ID string `json:"id"` Settings InstallationAdminSettings `json:"settings"` } -func GetInstallationAdminData(config common.Config) (*InstallationAdminData, error) { +func GetInstallationAdminData(config common.Config) (*Data, error) { resp, err := http.Get(fmt.Sprintf("%s/data", config.Server)) if err != nil { return nil, err @@ -35,7 +42,7 @@ func GetInstallationAdminData(config common.Config) (*InstallationAdminData, err return nil, err } - var data InstallationAdminData + var data Data if err := json.Unmarshal(body, &data); err != nil { return nil, err } diff --git a/components/server/src/installation-admin/installation-admin-controller.ts b/components/server/src/installation-admin/installation-admin-controller.ts index d30d2ceaa79bfa..824ab105513738 100644 --- a/components/server/src/installation-admin/installation-admin-controller.ts +++ b/components/server/src/installation-admin/installation-admin-controller.ts @@ -6,17 +6,25 @@ import { injectable, inject } from 'inversify'; import * as express from 'express'; -import { InstallationAdminDB } from '@gitpod/gitpod-db/lib'; +import { InstallationAdminDB, UserDB, WorkspaceDB } from '@gitpod/gitpod-db/lib'; +import { Data } from '@gitpod/gitpod-protocol' @injectable() export class InstallationAdminController { @inject(InstallationAdminDB) protected readonly installationAdminDb: InstallationAdminDB; + @inject(UserDB) protected readonly userDb: UserDB + @inject(WorkspaceDB) protected readonly workspaceDb: WorkspaceDB public create(): express.Application { const app = express(); app.get('/data', async (req: express.Request, res: express.Response) => { - const data = await this.installationAdminDb.getData(); + const data: Data = { + installationAdmin: await this.installationAdminDb.getData(), + totalUsers: await this.userDb.getUserCount(true), + totalWorkspaces: await this.workspaceDb.getWorkspaceCount(), + totalInstances: await this.workspaceDb.getInstanceCount(), + } as Data; res.status(200).json(data); });