diff --git a/components/gitpod-db/src/typeorm/entity/db-workspace-instance.ts b/components/gitpod-db/src/typeorm/entity/db-workspace-instance.ts index fec4e758e7fa1a..8a41f364423325 100644 --- a/components/gitpod-db/src/typeorm/entity/db-workspace-instance.ts +++ b/components/gitpod-db/src/typeorm/entity/db-workspace-instance.ts @@ -107,5 +107,5 @@ export class DBWorkspaceInstance implements WorkspaceInstance { default: "", transformer: Transformer.MAP_EMPTY_STR_TO_UNDEFINED, }) - attributedTeamId?: string; + usageAttributionId?: string; } diff --git a/components/gitpod-db/src/typeorm/migration/1655988518555-WorkspaceInstanceUsageAttributionId.ts b/components/gitpod-db/src/typeorm/migration/1655988518555-WorkspaceInstanceUsageAttributionId.ts new file mode 100644 index 00000000000000..a1656ee8493cae --- /dev/null +++ b/components/gitpod-db/src/typeorm/migration/1655988518555-WorkspaceInstanceUsageAttributionId.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { MigrationInterface, QueryRunner } from "typeorm"; +import { columnExists, indexExists } from "./helper/helper"; + +const TABLE_NAME = "d_b_workspace_instance"; +const COLUMN_NAME = "usageAttributionId"; +const INDEX_NAME = "ind_usageAttributionId"; + +export class WorkspaceInstanceUsageAttributionId1655988518555 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + if (!(await columnExists(queryRunner, TABLE_NAME, COLUMN_NAME))) { + await queryRunner.query( + `ALTER TABLE ${TABLE_NAME} ADD COLUMN ${COLUMN_NAME} varchar(60) NOT NULL DEFAULT '', ALGORITHM=INPLACE, LOCK=NONE`, + ); + } + if (!(await indexExists(queryRunner, TABLE_NAME, INDEX_NAME))) { + await queryRunner.query(`CREATE INDEX ${INDEX_NAME} ON ${TABLE_NAME} (${COLUMN_NAME})`); + } + } + + public async down(queryRunner: QueryRunner): Promise { + if (await columnExists(queryRunner, TABLE_NAME, COLUMN_NAME)) { + await queryRunner.query( + `ALTER TABLE ${TABLE_NAME} DROP COLUMN ${COLUMN_NAME}, ALGORITHM=INPLACE, LOCK=NONE`, + ); + } + } +} diff --git a/components/gitpod-db/src/workspace-db.spec.db.ts b/components/gitpod-db/src/workspace-db.spec.db.ts index 7aa0a8a3717af1..7f655f49035dae 100644 --- a/components/gitpod-db/src/workspace-db.spec.db.ts +++ b/components/gitpod-db/src/workspace-db.spec.db.ts @@ -66,7 +66,7 @@ class WorkspaceDBSpec { ideImage: "unknown", }, deleted: false, - attributedTeamId: undefined, + usageAttributionId: undefined, }; readonly wsi2: WorkspaceInstance = { workspaceId: this.ws.id, @@ -89,7 +89,7 @@ class WorkspaceDBSpec { ideImage: "unknown", }, deleted: false, - attributedTeamId: undefined, + usageAttributionId: undefined, }; readonly ws2: Workspace = { id: "2", @@ -127,7 +127,7 @@ class WorkspaceDBSpec { ideImage: "unknown", }, deleted: false, - attributedTeamId: undefined, + usageAttributionId: undefined, }; readonly ws3: Workspace = { @@ -165,7 +165,7 @@ class WorkspaceDBSpec { ideImage: "unknown", }, deleted: false, - attributedTeamId: undefined, + usageAttributionId: undefined, }; async before() { diff --git a/components/gitpod-protocol/src/workspace-instance.ts b/components/gitpod-protocol/src/workspace-instance.ts index 1cf2a0482ce112..c5370b521a3b06 100644 --- a/components/gitpod-protocol/src/workspace-instance.ts +++ b/components/gitpod-protocol/src/workspace-instance.ts @@ -64,11 +64,10 @@ export interface WorkspaceInstance { workspaceClass?: string; /** - * Identifies the team to which this instance's runtime should be attributed to + * Identifies the team or user to which this instance's runtime should be attributed to * (e.g. for usage analytics or billing purposes). - * If unset, the usage should be attributed to the workspace's owner (ws.ownerId). */ - attributedTeamId?: string; + usageAttributionId?: string; } // WorkspaceInstanceStatus describes the current state of a workspace instance diff --git a/components/server/src/user/user-service.ts b/components/server/src/user/user-service.ts index 7f9232fb2140f2..ee8f5ea73aa81f 100644 --- a/components/server/src/user/user-service.ts +++ b/components/server/src/user/user-service.ts @@ -207,25 +207,24 @@ export class UserService { } /** - * Identifies the team to which a workspace instance's running time should be attributed to + * Identifies the team or user to which a workspace instance's running time should be attributed to * (e.g. for usage analytics or billing purposes). - * If no specific team is identified, the usage will be attributed to the user instead (default). * * @param user * @param projectId */ - async getWorkspaceUsageAttributionTeamId(user: User, projectId?: string): Promise { + async getWorkspaceUsageAttributionId(user: User, projectId?: string): Promise { if (!projectId) { // No project -- attribute to the user. - return undefined; + return `user:${user.id}`; } const project = await this.projectDb.findProjectById(projectId); if (!project?.teamId) { // The project doesn't exist, or it isn't owned by a team -- attribute to the user. - return undefined; + return `user:${user.id}`; } // Attribute workspace usage to the team that currently owns this project. - return project.teamId; + return `team:${project.teamId}`; } /** diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 34f83daff455ab..7c6dac66b08d97 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -722,7 +722,7 @@ export class WorkspaceStarter { configuration.featureFlags = featureFlags; } - const attributedTeamId = await this.userService.getWorkspaceUsageAttributionTeamId(user, workspace.projectId); + const usageAttributionId = await this.userService.getWorkspaceUsageAttributionId(user, workspace.projectId); const now = new Date().toISOString(); const instance: WorkspaceInstance = { @@ -737,7 +737,7 @@ export class WorkspaceStarter { phase: "preparing", }, configuration, - attributedTeamId, + usageAttributionId, }; if (WithReferrerContext.is(workspace.context)) { this.analytics.track({