Skip to content

Commit d4fd759

Browse files
committed
move getPrimaryEmail to common and clean up
1 parent 7664094 commit d4fd759

File tree

13 files changed

+47
-87
lines changed

13 files changed

+47
-87
lines changed

components/dashboard/src/admin/UserSearch.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { AdminPageHeader } from "./AdminPageHeader";
1616
import UserDetail from "./UserDetail";
1717
import searchIcon from "../icons/search.svg";
1818
import Tooltip from "../components/Tooltip";
19+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
1920

2021
export default function UserSearch() {
2122
const location = useLocation();
@@ -129,7 +130,7 @@ function UserEntry(p: { user: User }) {
129130
if (!p) {
130131
return <></>;
131132
}
132-
const email = User.getPrimaryEmail(p.user) || "---";
133+
const email = getPrimaryEmail(p.user) || "---";
133134
return (
134135
<Link key={p.user.id} to={"/admin/users/" + p.user.id} data-analytics='{"button_type":"sidebar_menu"}'>
135136
<div className="rounded-xl whitespace-nowrap flex space-x-2 py-6 px-6 w-full justify-between hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-kumquat-light group">

components/dashboard/src/data/featureflag-query.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
78
import { useQuery } from "@tanstack/react-query";
89
import { getExperimentsClient } from "../experiments/client";
910
import { useCurrentProject } from "../projects/project-context";
@@ -40,7 +41,10 @@ export const useFeatureFlag = <K extends keyof FeatureFlags>(featureFlag: K): Fe
4041

4142
const query = useQuery(queryKey, async () => {
4243
const flagValue = await getExperimentsClient().getValueAsync(featureFlag, featureFlags[featureFlag], {
43-
user,
44+
user: user && {
45+
id: user.id,
46+
email: getPrimaryEmail(user),
47+
},
4448
projectId: project?.id,
4549
teamId: org?.id,
4650
teamName: org?.name,

components/gitpod-db/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
],
3737
"dependencies": {
3838
"@gitpod/gitpod-protocol": "0.1.5",
39+
"@gitpod/public-api-common": "0.1.5",
3940
"@jmondi/oauth2-server": "^2.6.1",
4041
"mysql": "^2.18.1",
4142
"reflect-metadata": "^0.1.13",

components/gitpod-db/src/typeorm/team-db-impl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
TeamMemberInfo,
1111
TeamMemberRole,
1212
TeamMembershipInvite,
13-
User,
1413
} from "@gitpod/gitpod-protocol";
1514
import { ErrorCodes, ApplicationError } from "@gitpod/gitpod-protocol/lib/messaging/error";
1615
import { randomBytes } from "crypto";
@@ -26,6 +25,7 @@ import { DBOrgSettings } from "./entity/db-team-settings";
2625
import { DBUser } from "./entity/db-user";
2726
import { TransactionalDBImpl } from "./transactional-db-impl";
2827
import { TypeORM } from "./typeorm";
28+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
2929

3030
@injectable()
3131
export class TeamDBImpl extends TransactionalDBImpl<TeamDB> implements TeamDB {
@@ -101,7 +101,7 @@ export class TeamDBImpl extends TransactionalDBImpl<TeamDB> implements TeamDB {
101101
return {
102102
userId: u.id,
103103
fullName: u.fullName || u.name,
104-
primaryEmail: User.getPrimaryEmail(u),
104+
primaryEmail: getPrimaryEmail(u),
105105
avatarUrl: u.avatarUrl,
106106
role: m.role,
107107
memberSince: m.creationTime,

components/gitpod-protocol/src/experiments/configcat.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import { Attributes, Client } from "./types";
88
import { User as ConfigCatUser } from "configcat-common/lib/RolloutEvaluator";
99
import { IConfigCatClient } from "configcat-common/lib/ConfigCatClient";
10-
import { User } from "../protocol";
1110

1211
export const USER_ID_ATTRIBUTE = "user_id";
1312
export const PROJECT_ID_ATTRIBUTE = "project_id";
@@ -37,7 +36,7 @@ export class ConfigCatClient implements Client {
3736

3837
export function attributesToUser(attributes: Attributes): ConfigCatUser {
3938
const userId = attributes.user?.id || "";
40-
const email = User.is(attributes.user) ? User.getPrimaryEmail(attributes.user) : attributes.user?.email || "";
39+
const email = attributes.user?.email || "";
4140

4241
const custom: { [key: string]: string } = {};
4342
if (userId) {

components/gitpod-protocol/src/experiments/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { BillingTier, User } from "../protocol";
7+
import { BillingTier } from "../protocol";
88

99
export const Client = Symbol("Client");
1010

1111
// Attributes define attributes which can be used to segment audiences.
1212
// Set the attributes which you want to use to group audiences into.
1313
export interface Attributes {
1414
// user.id is mapped to ConfigCat's "identifier" + "custom.user_id"
15-
user?: User | { id: string; email?: string };
15+
user?: { id: string; email?: string };
1616

1717
// The BillingTier of this particular user
1818
billingTier?: BillingTier;

components/gitpod-protocol/src/protocol.ts

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -71,58 +71,6 @@ export namespace User {
7171
return user.identities.find((id) => id.authProviderId === authProviderId);
7272
}
7373

74-
/**
75-
* Returns a primary email address of a user.
76-
*
77-
* For accounts owned by an organization, it returns the email of the most recently used SSO identity.
78-
*
79-
* For personal accounts, first it looks for a email stored by the user, and falls back to any of the Git provider identities.
80-
*
81-
* @param user
82-
* @returns A primaryEmail, or undefined.
83-
*/
84-
export function getPrimaryEmail(user: User): string | undefined {
85-
// If the accounts is owned by an organization, use the email of the most recently
86-
// used SSO identity.
87-
if (User.isOrganizationOwned(user)) {
88-
const compareTime = (a?: string, b?: string) => (a || "").localeCompare(b || "");
89-
const recentlyUsedSSOIdentity = user.identities
90-
.sort((a, b) => compareTime(a.lastSigninTime, b.lastSigninTime))
91-
// optimistically pick the most recent one
92-
.reverse()[0];
93-
return recentlyUsedSSOIdentity?.primaryEmail;
94-
}
95-
96-
// In case of a personal account, check for the email stored by the user.
97-
if (!isOrganizationOwned(user) && user.additionalData?.profile?.emailAddress) {
98-
return user.additionalData?.profile?.emailAddress;
99-
}
100-
101-
// Otherwise pick any
102-
// FIXME(at) this is still not correct, as it doesn't distinguish between
103-
// sign-in providers and additional Git hosters.
104-
const identities = user.identities.filter((i) => !!i.primaryEmail);
105-
if (identities.length <= 0) {
106-
return undefined;
107-
}
108-
109-
return identities[0].primaryEmail || undefined;
110-
}
111-
112-
export function getName(user: User): string | undefined {
113-
const name = user.fullName || user.name;
114-
if (name) {
115-
return name;
116-
}
117-
118-
for (const id of user.identities) {
119-
if (id.authName !== "") {
120-
return id.authName;
121-
}
122-
}
123-
return undefined;
124-
}
125-
12674
export function isOrganizationOwned(user: User) {
12775
return !!user.organizationId;
12876
}
@@ -1396,12 +1344,6 @@ export interface CommitInfo {
13961344
authorDate?: string;
13971345
}
13981346

1399-
export namespace Repository {
1400-
export function fullRepoName(repo: Repository): string {
1401-
return `${repo.host}/${repo.owner}/${repo.name}`;
1402-
}
1403-
}
1404-
14051347
export interface WorkspaceInstancePortsChangedEvent {
14061348
type: "PortsChanged";
14071349
instanceID: string;
@@ -1446,17 +1388,6 @@ export namespace WorkspaceCreationResult {
14461388
}
14471389
}
14481390

1449-
export interface UserMessage {
1450-
readonly id: string;
1451-
readonly title?: string;
1452-
/**
1453-
* date from where on this message should be shown
1454-
*/
1455-
readonly from?: string;
1456-
readonly content?: string;
1457-
readonly url?: string;
1458-
}
1459-
14601391
export interface AuthProviderInfo {
14611392
readonly authProviderId: string;
14621393
readonly authProviderType: string;

components/server/src/analytics.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IAnalyticsWriter, IdentifyMessage, PageMessage, TrackMessage } from "@g
99
import * as crypto from "crypto";
1010
import { clientIp } from "./express-util";
1111
import { ctxTrySubjectId } from "./util/request-context";
12+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
1213

1314
export async function trackLogin(user: User, request: Request, authHost: string, analytics: IAnalyticsWriter) {
1415
// make new complete identify call for each login
@@ -85,7 +86,7 @@ async function fullIdentify(user: User, request: Request, analytics: IAnalyticsW
8586
},
8687
traits: {
8788
...resolveIdentities(user),
88-
email: User.getPrimaryEmail(user) || "",
89+
email: getPrimaryEmail(user) || "",
8990
full_name: user.fullName,
9091
created_at: user.creationDate,
9192
unsubscribed_onboarding: user.additionalData?.emailNotificationSettings?.allowsOnboardingMail === false,

components/server/src/api/verification-service-api.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ctxUserId } from "../util/request-context";
2020
import { UserService } from "../user/user-service";
2121
import { formatPhoneNumber } from "../user/phone-numbers";
2222
import { validate as uuidValidate } from "uuid";
23+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
2324

2425
@injectable()
2526
export class VerificationServiceAPI implements ServiceImpl<typeof VerificationServiceInterface> {
@@ -42,7 +43,10 @@ export class VerificationServiceAPI implements ServiceImpl<typeof VerificationSe
4243
"phoneVerificationByCall",
4344
false,
4445
{
45-
user,
46+
user: {
47+
id: user.id,
48+
email: getPrimaryEmail(user),
49+
},
4650
},
4751
);
4852
const channel = phoneVerificationByCall ? "call" : "sms";

components/server/src/auth/verification-service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { v4 as uuidv4, validate as uuidValidate } from "uuid";
1616
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
1717
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
1818
import { UserService } from "../user/user-service";
19+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
1920

2021
interface VerificationEndpoint {
2122
sendToken(phoneNumber: string, channel: "sms" | "call"): Promise<string>;
@@ -134,7 +135,10 @@ export class VerificationService {
134135
"isPhoneVerificationEnabled",
135136
false,
136137
{
137-
user,
138+
user: {
139+
id: user.id,
140+
email: getPrimaryEmail(user),
141+
},
138142
},
139143
);
140144
return isPhoneVerificationEnabled;

components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { RepositoryProvider } from "../repohost/repository-provider";
1111
import { BitbucketServerApi } from "./bitbucket-server-api";
1212
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1313
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
14+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
1415

1516
@injectable()
1617
export class BitbucketServerRepositoryProvider implements RepositoryProvider {
@@ -150,7 +151,10 @@ export class BitbucketServerRepositoryProvider implements RepositoryProvider {
150151
"repositoryFinderSearch",
151152
false,
152153
{
153-
user,
154+
user: {
155+
id: user.id,
156+
email: getPrimaryEmail(user),
157+
},
154158
},
155159
);
156160

components/server/src/ide-service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
IDEServiceDefinition,
1313
ResolveWorkspaceConfigResponse,
1414
} from "@gitpod/ide-service-api/lib/ide.pb";
15+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
1516
import { inject, injectable } from "inversify";
1617
import { AuthorizationService } from "./user/authorization-service";
1718

@@ -87,7 +88,7 @@ export class IDEService {
8788
workspaceConfig: JSON.stringify(workspace.config),
8889
user: {
8990
id: user.id,
90-
email: User.getPrimaryEmail(user),
91+
email: getPrimaryEmail(user),
9192
},
9293
};
9394
for (let attempt = 0; attempt < 15; attempt++) {

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ import { ScmService } from "../scm/scm-service";
144144
import { ContextService } from "./context-service";
145145
import { runWithRequestContext, runWithSubjectId } from "../util/request-context";
146146
import { SubjectId } from "../auth/subject-id";
147+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
147148

148149
// shortcut
149150
export const traceWI = (ctx: TraceContext, wi: Omit<LogContext, "userId">) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager
@@ -460,7 +461,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
460461
"phoneVerificationByCall",
461462
false,
462463
{
463-
user,
464+
user: {
465+
id: user.id,
466+
email: getPrimaryEmail(user),
467+
},
464468
},
465469
);
466470

@@ -2413,7 +2417,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
24132417
private async guardWithFeatureFlag(flagName: string, user: User, teamId: string) {
24142418
// Guard method w/ a feature flag check
24152419
const isEnabled = await getExperimentsClientForBackend().getValueAsync(flagName, false, {
2416-
user: user,
2420+
user: {
2421+
id: user.id,
2422+
email: getPrimaryEmail(user),
2423+
},
24172424
teamId,
24182425
});
24192426
if (!isEnabled) {
@@ -2498,7 +2505,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
24982505

24992506
async getIDEOptions(ctx: TraceContext): Promise<IDEOptions> {
25002507
const user = await this.checkUser("identifyUser");
2501-
const email = User.getPrimaryEmail(user);
2508+
const email = getPrimaryEmail(user);
25022509
const ideConfig = await this.ideService.getIDEConfig({ user: { id: user.id, email } });
25032510
return ideConfig.ideOptions;
25042511
}
@@ -2620,7 +2627,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
26202627
await this.auth.checkPermissionOnOrganization(user.id, "write_billing", attrId.teamId);
26212628

26222629
//TODO billing email should be editable within the org
2623-
const billingEmail = User.getPrimaryEmail(user);
2630+
const billingEmail = getPrimaryEmail(user);
26242631
const billingName = org.name;
26252632

26262633
let customer: StripeCustomer | undefined;
@@ -2821,7 +2828,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28212828
throw new ApplicationError(ErrorCodes.NOT_FOUND, "Organization not found.");
28222829
}
28232830
const isMemberUsageEnabled = await getExperimentsClientForBackend().getValueAsync("member_usage", false, {
2824-
user: user,
2831+
user: {
2832+
id: user.id,
2833+
email: getPrimaryEmail(user),
2834+
},
28252835
teamId: attributionId.teamId,
28262836
});
28272837
if (isMemberUsageEnabled && member.role !== "owner") {

0 commit comments

Comments
 (0)