Skip to content

Commit

Permalink
[server] Add trackEvent to support dashboard analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
csweichel committed Jul 9, 2021
1 parent f779dec commit 53bc9e9
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 38 deletions.
40 changes: 40 additions & 0 deletions components/gitpod-protocol/src/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2021 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 { Without } from "./util/without";

export const IAnalyticsWriter = Symbol("IAnalyticsWriter");

type Identity =
| { userId: string | number }
| { userId?: string | number; anonymousId: string | number };

interface Message {
messageId?: string;
}

export type IdentifyMessage = Message & Identity & {
traits?: any;
timestamp?: Date;
context?: any;
};

export type TrackMessage = Message & Identity & {
event: string;
properties?: any;
timestamp?: Date;
context?: any;
};

export type RemoteTrackMessage = Without<TrackMessage, "timestamp" | "userId" | "anonymousId">;

export interface IAnalyticsWriter {

identify(msg: IdentifyMessage): void;

track(msg: TrackMessage): void;

}
6 changes: 6 additions & 0 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Emitter } from './util/event';
import { AccountStatement, CreditAlert } from './accounting-protocol';
import { GithubUpgradeURL, PlanCoupon } from './payment-protocol';
import { TeamSubscription, TeamSubscriptionSlot, TeamSubscriptionSlotResolved } from './team-subscription-protocol';
import { RemoteTrackMessage } from './analytics';

export interface GitpodClient {
onInstanceUpdate(instance: WorkspaceInstance): void;
Expand Down Expand Up @@ -212,6 +213,11 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
createProject(params: CreateProjectParams): Promise<Project>;
getProjects(teamId: string): Promise<ProjectInfo[]>;
getPrebuilds(teamId: string, project: string): Promise<PrebuildInfo[]>;

/**
* Analytics
*/
trackEvent(event: RemoteTrackMessage): Promise<void>;
}

export interface CreateProjectParams {
Expand Down
31 changes: 1 addition & 30 deletions components/gitpod-protocol/src/util/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,9 @@
*/

import Analytics = require("analytics-node");
import { IAnalyticsWriter, IdentifyMessage, TrackMessage } from "../analytics";
import { log } from './logging';

export const IAnalyticsWriter = Symbol("IAnalyticsWriter");

type Identity =
| { userId: string | number }
| { userId?: string | number; anonymousId: string | number };

interface Message {
messageId?: string;
}

export type IdentifyMessage = Message & Identity & {
traits?: any;
timestamp?: Date;
context?: any;
};

export type TrackMessage = Message & Identity & {
event: string;
properties?: any;
timestamp?: Date;
context?: any;
};

export function newAnalyticsWriterFromEnv(): IAnalyticsWriter {
switch (process.env.GITPOD_ANALYTICS_WRITER) {
Expand All @@ -41,14 +20,6 @@ export function newAnalyticsWriterFromEnv(): IAnalyticsWriter {
}
}

export interface IAnalyticsWriter {

identify(msg: IdentifyMessage): void;

track(msg: TrackMessage): void;

}

class SegmentAnalyticsWriter implements IAnalyticsWriter {

protected readonly analytics: Analytics;
Expand Down
2 changes: 1 addition & 1 deletion components/server/src/auth/login-completion-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { HostContextProvider } from './host-context-provider';
import { AuthProviderService } from './auth-provider-service';
import { TosFlow } from '../terms/tos-flow';
import { increaseLoginCounter } from '../../src/prometheus-metrics';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics';

/**
* The login completion handler pulls the strings between the OAuth2 flow, the ToS flow, and the session management.
Expand Down
2 changes: 2 additions & 0 deletions components/server/src/auth/rate-limiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ function readConfig(): RateLimiterConfig {
"createProject": { group: "default", points: 1 },
"getProjects": { group: "default", points: 1 },
"getPrebuilds": { group: "default", points: 1 },

"trackEvent": { group: "default", points: 1 },
};

const fromEnv = JSON.parse(process.env.RATE_LIMITER_CONFIG || "{}")
Expand Down
3 changes: 2 additions & 1 deletion components/server/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ import { ContentServiceStorageClient } from './storage/content-service-client';
import { IDEPluginServiceClient } from '@gitpod/content-service/lib/ideplugin_grpc_pb';
import { GitTokenScopeGuesser } from './workspace/git-token-scope-guesser';
import { GitTokenValidator } from './workspace/git-token-validator';
import { newAnalyticsWriterFromEnv, IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics';
import { newAnalyticsWriterFromEnv } from '@gitpod/gitpod-protocol/lib/util/analytics';
import { OAuthController } from './oauth-server/oauth-controller';
import { ImageBuildPrefixContextParser } from './workspace/imagebuild-prefix-context-parser';
import { AdditionalContentPrefixContextParser } from './workspace/additional-content-prefix-context-parser';
import { WorkspaceLogService } from './workspace/workspace-log-service';
import { HeadlessLogController } from './workspace/headless-log-controller';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics';

export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(Env).toSelf().inSingletonScope();
Expand Down
2 changes: 1 addition & 1 deletion components/server/src/user/user-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { GitpodToken, GitpodTokenType, User } from "@gitpod/gitpod-protocol";
import { HostContextProvider } from "../auth/host-context-provider";
import { AuthFlow } from "../auth/auth-provider";
import { LoginCompletionHandler } from "../auth/login-completion-handler";
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/util/analytics";
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
import { TosCookie } from "./tos-cookie";
import { TosFlow } from "../terms/tos-flow";
import { increaseLoginCounter } from '../../src/prometheus-metrics';
Expand Down
2 changes: 1 addition & 1 deletion components/server/src/user/user-deletion-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-pr
import { StopWorkspaceRequest, StopWorkspacePolicy } from "@gitpod/ws-manager/lib";
import { WorkspaceDeletionService } from "../workspace/workspace-deletion-service";
import { AuthProviderService } from "../auth/auth-provider-service";
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics';

@injectable()
export class UserDeletionService {
Expand Down
24 changes: 23 additions & 1 deletion components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TeamSubscription, TeamSubscriptionSlot, TeamSubscriptionSlotResolved }
import { Cancelable } from '@gitpod/gitpod-protocol/lib/util/cancelable';
import { log, LogContext } from '@gitpod/gitpod-protocol/lib/util/logging';
import { TraceContext } from '@gitpod/gitpod-protocol/lib/util/tracing';
import { RemoteTrackMessage, TrackMessage } from '@gitpod/gitpod-protocol/lib/analytics';
import { ImageBuilderClientProvider, LogsRequest } from '@gitpod/image-builder/lib';
import { WorkspaceManagerClientProvider } from '@gitpod/ws-manager/lib/client-provider';
import { ControlPortRequest, DescribeWorkspaceRequest, MarkActiveRequest, PortSpec, PortVisibility as ProtoPortVisibility, StopWorkspacePolicy, StopWorkspaceRequest } from '@gitpod/ws-manager/lib/core_pb';
Expand All @@ -26,7 +27,7 @@ import * as opentracing from 'opentracing';
import { URL } from 'url';
import * as uuidv4 from 'uuid/v4';
import { Disposable, ResponseError } from 'vscode-jsonrpc';
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/util/analytics";
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
import { AuthProviderService } from '../auth/auth-provider-service';
import { HostContextProvider } from '../auth/host-context-provider';
import { GuardedResource, ResourceAccessGuard, ResourceAccessOp } from '../auth/resource-access';
Expand Down Expand Up @@ -1775,6 +1776,27 @@ export class GitpodServerImpl<Client extends GitpodClient, Server extends Gitpod
}
}

public async trackEvent(event: RemoteTrackMessage): Promise<void> {
if (!this.user) {
// we cannot track events if don't know the user, because we have no sensible means
// to produce a correlatable anonymousId.
return;
}

// Beware: DO NOT just event... the message, but consume it individually as the message is coming from
// the wire and we have no idea what's in it. Even passing the context and properties directly
// is questionable. Considering we're handing down the msg and do not know how the analytics library
// handles potentially broken or malicious input, we better err on the side of caution.
const msg: TrackMessage = {
userId: this.user.id,
event: event.event,
messageId: event.messageId,
context: event.context,
properties: event.properties,
}
this.analytics.track(msg);
}

async getTerms(): Promise<Terms> {
// Terms are publicly available, thus no user check here.

Expand Down
2 changes: 1 addition & 1 deletion components/server/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CloneTargetMode, FileDownloadInitializer, GitAuthMethod, GitConfig, Git
import { CompositeInitializer, FromBackupInitializer } from "@gitpod/content-service/lib/initializer_pb";
import { DBUser, DBWithTracing, TracedUserDB, TracedWorkspaceDB, UserDB, WorkspaceDB } from '@gitpod/gitpod-db/lib';
import { CommitContext, Disposable, GitpodToken, GitpodTokenType, IssueContext, NamedWorkspaceFeatureFlag, PullRequestContext, RefType, SnapshotContext, StartWorkspaceResult, User, UserEnvVar, UserEnvVarValue, WithEnvvarsContext, WithPrebuild, Workspace, WorkspaceContext, WorkspaceImageSource, WorkspaceImageSourceDocker, WorkspaceImageSourceReference, WorkspaceInstance, WorkspaceInstanceConfiguration, WorkspaceInstanceStatus, WorkspaceProbeContext, Permission, HeadlessLogEvent, HeadlessWorkspaceEventType, DisposableCollection, AdditionalContentContext, ImageConfigFile, EmailType } from "@gitpod/gitpod-protocol";
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics';
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { BuildRegistryAuth, BuildRegistryAuthSelective, BuildRegistryAuthTotal, BuildRequest, BuildResponse, BuildSource, BuildSourceDockerfile, BuildSourceReference, BuildStatus, ImageBuilderClientProvider, ResolveBaseImageRequest, ResolveWorkspaceImageRequest } from "@gitpod/image-builder/lib";
Expand Down
2 changes: 1 addition & 1 deletion components/ws-manager-bridge/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { UserDB } from "@gitpod/gitpod-db/lib/user-db";
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
import { HeadlessLogEvent } from "@gitpod/gitpod-protocol/lib/headless-workspace-log";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/util/analytics";
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
import { TracedWorkspaceDB, TracedUserDB, DBWithTracing } from '@gitpod/gitpod-db/lib/traced-db';
import { PrometheusMetricsExporter } from "./prometheus-metrics-exporter";
import { ClientProvider, WsmanSubscriber } from "./wsman-subscriber";
Expand Down
3 changes: 2 additions & 1 deletion components/ws-manager-bridge/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { filePathTelepresenceAware } from '@gitpod/gitpod-protocol/lib/env';
import { WorkspaceManagerClientProvider } from '@gitpod/ws-manager/lib/client-provider';
import { WorkspaceManagerClientProviderCompositeSource, WorkspaceManagerClientProviderDBSource, WorkspaceManagerClientProviderSource } from '@gitpod/ws-manager/lib/client-provider-source';
import { ClusterService, ClusterServiceServer } from './cluster-service-server';
import { IAnalyticsWriter, newAnalyticsWriterFromEnv } from '@gitpod/gitpod-protocol/lib/util/analytics';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics';
import { newAnalyticsWriterFromEnv } from '@gitpod/gitpod-protocol/lib/util/analytics';

export const containerModule = new ContainerModule(bind => {

Expand Down

0 comments on commit 53bc9e9

Please sign in to comment.