Skip to content

Commit e4ccbf0

Browse files
authored
GetAuthenticatedUser in Dashboard (#19142)
* [dashboard] remove unused service mock * [dashboard] use `GetAuthenticatedUser` instead of `getLoggedInUser` * fixup: override workspaceAutostartOptions also fix toDurationString call * fixup: move `isOnboardingUser` to dashboard * fixup: move getProfile from protocol to common * fixup fromWorkspaceAutostartOption * move getPrimaryEmail to common and clean up * rm getProfile from protocol, use ProfileDetails * fixup missing leeway dependencies * fix getPrimaryEmail * fix resetting workspace timeout * [gitpod-db] remove dependency to `@gitpod/public-api-common` * cleanup BUILD.yaml
1 parent 0152b84 commit e4ccbf0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+954
-1091
lines changed

components/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"countries-list": "^2.6.1",
3131
"crypto-browserify": "3.12.0",
3232
"dayjs": "^1.11.5",
33+
"deepmerge": "^4.2.2",
3334
"file-saver": "^2.0.5",
3435
"idb-keyval": "^6.2.0",
3536
"js-cookie": "^3.0.1",

components/dashboard/src/AppNotifications.tsx

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
*/
66

77
import dayjs from "dayjs";
8-
import deepMerge from "deepmerge";
98
import { useCallback, useEffect, useState } from "react";
109
import Alert, { AlertType } from "./components/Alert";
1110
import { useUserLoader } from "./hooks/use-user-loader";
12-
import { getGitpodService } from "./service/service";
1311
import { isGitpodIo } from "./utils";
1412
import { trackEvent } from "./Analytics";
13+
import { useUpdateCurrentUserMutation } from "./data/current-user/update-mutation";
14+
import { User as UserProtocol } from "@gitpod/gitpod-protocol";
15+
import { User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";
1516

1617
const KEY_APP_DISMISSED_NOTIFICATIONS = "gitpod-app-notifications-dismissed";
1718
const PRIVACY_POLICY_LAST_UPDATED = "2023-10-17";
@@ -24,59 +25,60 @@ interface Notification {
2425
onClose?: () => void;
2526
}
2627

27-
const UPDATED_PRIVACY_POLICY: Notification = {
28-
id: "privacy-policy-update",
29-
type: "info",
30-
preventDismiss: true,
31-
onClose: async () => {
32-
let dismissSuccess = false;
33-
try {
34-
const userUpdates = { additionalData: { profile: { acceptedPrivacyPolicyDate: dayjs().toISOString() } } };
35-
const previousUser = await getGitpodService().server.getLoggedInUser();
36-
const updatedUser = await getGitpodService().server.updateLoggedInUser(
37-
deepMerge(previousUser, userUpdates),
38-
);
39-
dismissSuccess = !!updatedUser;
40-
} catch (err) {
41-
console.error("Failed to update user's privacy policy acceptance date", err);
42-
dismissSuccess = false;
43-
} finally {
44-
trackEvent("privacy_policy_update_accepted", {
45-
path: window.location.pathname,
46-
success: dismissSuccess,
47-
});
48-
}
49-
},
50-
message: (
51-
<span className="text-md">
52-
We've updated our Privacy Policy. You can review it{" "}
53-
<a className="gp-link" href="https://www.gitpod.io/privacy" target="_blank" rel="noreferrer">
54-
here
55-
</a>
56-
.
57-
</span>
58-
),
28+
const UPDATED_PRIVACY_POLICY = (updateUser: (user: Partial<UserProtocol>) => Promise<User>) => {
29+
return {
30+
id: "privacy-policy-update",
31+
type: "info",
32+
preventDismiss: true,
33+
onClose: async () => {
34+
let dismissSuccess = false;
35+
try {
36+
const updatedUser = await updateUser({
37+
additionalData: { profile: { acceptedPrivacyPolicyDate: dayjs().toISOString() } },
38+
});
39+
dismissSuccess = !!updatedUser;
40+
} catch (err) {
41+
console.error("Failed to update user's privacy policy acceptance date", err);
42+
dismissSuccess = false;
43+
} finally {
44+
trackEvent("privacy_policy_update_accepted", {
45+
path: window.location.pathname,
46+
success: dismissSuccess,
47+
});
48+
}
49+
},
50+
message: (
51+
<span className="text-md">
52+
We've updated our Privacy Policy. You can review it{" "}
53+
<a className="gp-link" href="https://www.gitpod.io/privacy" target="_blank" rel="noreferrer">
54+
here
55+
</a>
56+
.
57+
</span>
58+
),
59+
} as Notification;
5960
};
6061

6162
export function AppNotifications() {
6263
const [topNotification, setTopNotification] = useState<Notification | undefined>(undefined);
6364
const { user, loading } = useUserLoader();
65+
const updateUser = useUpdateCurrentUserMutation();
6466

6567
useEffect(() => {
6668
const notifications = [];
6769
if (!loading && isGitpodIo()) {
6870
if (
69-
!user?.additionalData?.profile?.acceptedPrivacyPolicyDate ||
70-
new Date(PRIVACY_POLICY_LAST_UPDATED) > new Date(user.additionalData.profile.acceptedPrivacyPolicyDate)
71+
!user?.profile?.acceptedPrivacyPolicyDate ||
72+
new Date(PRIVACY_POLICY_LAST_UPDATED) > new Date(user.profile.acceptedPrivacyPolicyDate)
7173
) {
72-
notifications.push(UPDATED_PRIVACY_POLICY);
74+
notifications.push(UPDATED_PRIVACY_POLICY((u: Partial<UserProtocol>) => updateUser.mutateAsync(u)));
7375
}
7476
}
7577

7678
const dismissedNotifications = getDismissedNotifications();
7779
const topNotification = notifications.find((n) => !dismissedNotifications.includes(n.id));
7880
setTopNotification(topNotification);
79-
}, [loading, setTopNotification, user]);
81+
}, [loading, updateUser, setTopNotification, user]);
8082

8183
const dismissNotification = useCallback(() => {
8284
if (!topNotification) {

components/dashboard/src/Login.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useNeedsSetup } from "./dedicated-setup/use-needs-setup";
2323
import { AuthProviderDescription } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
2424
import { Button, ButtonProps } from "@podkit/buttons/Button";
2525
import { cn } from "@podkit/lib/cn";
26+
import { userClient } from "./service/public-api";
2627

2728
export function markLoggedIn() {
2829
document.cookie = GitpodCookie.generateCookie(window.location.hostname);
@@ -67,9 +68,11 @@ export const Login: FC<LoginProps> = ({ onLoggedIn }) => {
6768

6869
const updateUser = useCallback(async () => {
6970
await getGitpodService().reconnect();
70-
const user = await getGitpodService().server.getLoggedInUser();
71-
setUser(user);
72-
markLoggedIn();
71+
const { user } = await userClient.getAuthenticatedUser({});
72+
if (user) {
73+
setUser(user);
74+
markLoggedIn();
75+
}
7376
}, [setUser]);
7477

7578
const authorizeSuccessful = useCallback(async () => {

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/app/AdminRoute.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { useContext } from "react";
88
import { Redirect, Route } from "react-router";
99
import { UserContext } from "../user-context";
10+
import { User_RoleOrPermission } from "@gitpod/public-api/lib/gitpod/v1/user_pb";
1011

1112
// A wrapper for <Route> that redirects to the workspaces screen if the user isn't a admin.
1213
// This wrapper only accepts the component property
@@ -15,7 +16,7 @@ export function AdminRoute({ component }: any) {
1516
return (
1617
<Route
1718
render={({ location }: any) =>
18-
user?.rolesOrPermissions?.includes("admin") ? (
19+
user?.rolesOrPermissions?.includes(User_RoleOrPermission.ADMIN) ? (
1920
<Route component={component}></Route>
2021
) : (
2122
<Redirect

components/dashboard/src/app/AppBlockingFlows.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import { FC, lazy } from "react";
88
import { useShowDedicatedSetup } from "../dedicated-setup/use-show-dedicated-setup";
99
import { useCurrentUser } from "../user-context";
10-
import { MigrationPage, useShouldSeeMigrationPage } from "../whatsnew/MigrationPage";
1110
import { useShowUserOnboarding } from "../onboarding/use-show-user-onboarding";
1211
import { useHistory } from "react-router";
1312
import { useCurrentOrg } from "../data/organizations/orgs-query";
@@ -22,7 +21,6 @@ export const AppBlockingFlows: FC = ({ children }) => {
2221
const history = useHistory();
2322
const user = useCurrentUser();
2423
const org = useCurrentOrg();
25-
const shouldSeeMigrationPage = useShouldSeeMigrationPage();
2624
const showDedicatedSetup = useShowDedicatedSetup();
2725
const showUserOnboarding = useShowUserOnboarding();
2826

@@ -31,11 +29,6 @@ export const AppBlockingFlows: FC = ({ children }) => {
3129
return <></>;
3230
}
3331

34-
// If orgOnlyAttribution is enabled and the user hasn't been migrated, yet, we need to show the migration page
35-
if (shouldSeeMigrationPage) {
36-
return <MigrationPage />;
37-
}
38-
3932
// Handle dedicated setup if necessary
4033
if (showDedicatedSetup.showSetup) {
4134
return (

components/dashboard/src/app/AppRoutes.tsx

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

7-
import React, { useState } from "react";
7+
import React from "react";
88
import { Redirect, Route, Switch, useLocation } from "react-router";
99
import OAuthClientApproval from "../OauthClientApproval";
1010
import Menu from "../menu/Menu";
@@ -25,15 +25,13 @@ import {
2525
usagePathMain,
2626
} from "../user-settings/settings.routes";
2727
import { getURLHash, isGitpodIo } from "../utils";
28-
import { WhatsNew, shouldSeeWhatsNew } from "../whatsnew/WhatsNew";
2928
import { workspacesPathMain } from "../workspaces/workspaces.routes";
3029
import { AdminRoute } from "./AdminRoute";
3130
import { Blocked } from "./Blocked";
3231

3332
// TODO: Can we bundle-split/lazy load these like other pages?
3433
import { BlockedRepositories } from "../admin/BlockedRepositories";
3534
import { Heading1, Subheading } from "../components/typography/headings";
36-
import { useCurrentUser } from "../user-context";
3735
import PersonalAccessTokenCreateView from "../user-settings/PersonalAccessTokensCreateView";
3836
import { CreateWorkspacePage } from "../workspaces/CreateWorkspacePage";
3937
import { WebsocketClients } from "./WebsocketClients";
@@ -84,8 +82,6 @@ const ConfigurationDetailPage = React.lazy(
8482

8583
export const AppRoutes = () => {
8684
const hash = getURLHash();
87-
const user = useCurrentUser();
88-
const [isWhatsNewShown, setWhatsNewShown] = useState(user && shouldSeeWhatsNew(user));
8985
const location = useLocation();
9086
const repoConfigListAndDetail = useFeatureFlag("repoConfigListAndDetail");
9187

@@ -99,10 +95,6 @@ export const AppRoutes = () => {
9995
return <OAuthClientApproval />;
10096
}
10197

102-
if (isWhatsNewShown) {
103-
return <WhatsNew onClose={() => setWhatsNewShown(false)} />;
104-
}
105-
10698
// TODO: Try and encapsulate this in a route for "/" (check for hash in route component, render or redirect accordingly)
10799
const isCreation = location.pathname === "/" && hash !== "";
108100
if (isCreation) {

components/dashboard/src/components/AuthorizeGit.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { FC, useCallback, useContext } from "react";
88
import { Link } from "react-router-dom";
99
import { useAuthProviderDescriptions } from "../data/auth-providers/auth-provider-descriptions-query";
1010
import { openAuthorizeWindow } from "../provider-utils";
11-
import { getGitpodService } from "../service/service";
11+
import { userClient } from "../service/public-api";
1212
import { UserContext, useCurrentUser } from "../user-context";
1313
import { Button } from "./Button";
1414
import { Heading2, Heading3, Subheading } from "./typography/headings";
@@ -18,20 +18,23 @@ import { useIsOwner } from "../data/organizations/members-query";
1818
import { AuthProviderDescription } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
1919

2020
export function useNeedsGitAuthorization() {
21-
const authProviders = useAuthProviderDescriptions();
21+
const { data: authProviders } = useAuthProviderDescriptions();
2222
const user = useCurrentUser();
23-
if (!user || !authProviders.data) {
23+
if (!user || !authProviders) {
2424
return false;
2525
}
26-
return !authProviders.data.some((ap) => user.identities.some((i) => ap.id === i.authProviderId));
26+
return !authProviders.some((ap) => user.identities.some((i) => ap.id === i.authProviderId));
2727
}
2828

2929
export const AuthorizeGit: FC<{ className?: string }> = ({ className }) => {
3030
const { setUser } = useContext(UserContext);
3131
const owner = useIsOwner();
3232
const { data: authProviders } = useAuthProviderDescriptions();
33-
const updateUser = useCallback(() => {
34-
getGitpodService().server.getLoggedInUser().then(setUser);
33+
const updateUser = useCallback(async () => {
34+
const response = await userClient.getAuthenticatedUser({});
35+
if (response.user) {
36+
setUser(response.user);
37+
}
3538
}, [setUser]);
3639

3740
const connect = useCallback(
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { useQuery } from "@tanstack/react-query";
8+
import { userClient } from "../../service/public-api";
9+
import { GetAuthenticatedUserRequest, User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";
10+
11+
export const useAuthenticatedUser = () => {
12+
const query = useQuery<User>({
13+
queryKey: getAuthenticatedUserQueryKey(),
14+
queryFn: async () => {
15+
const params = new GetAuthenticatedUserRequest();
16+
const response = await userClient.getAuthenticatedUser(params);
17+
return response.user!;
18+
},
19+
});
20+
return query;
21+
};
22+
23+
export const getAuthenticatedUserQueryKey = () => ["authenticated-user", {}];

components/dashboard/src/data/current-user/update-mutation.ts

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

7-
import { User } from "@gitpod/gitpod-protocol";
7+
import { AdditionalUserData, User as UserProtocol } from "@gitpod/gitpod-protocol";
88
import { useMutation } from "@tanstack/react-query";
99
import { trackEvent } from "../../Analytics";
1010
import { getGitpodService } from "../../service/service";
1111
import { useCurrentUser } from "../../user-context";
12+
import { converter } from "../../service/public-api";
13+
import deepmerge from "deepmerge";
1214

13-
type UpdateCurrentUserArgs = Partial<User>;
15+
type UpdateCurrentUserArgs = Partial<UserProtocol>;
1416

1517
export const useUpdateCurrentUserMutation = () => {
1618
return useMutation({
1719
mutationFn: async (partialUser: UpdateCurrentUserArgs) => {
18-
return await getGitpodService().server.updateLoggedInUser(partialUser);
20+
const current = await getGitpodService().server.getLoggedInUser();
21+
const currentAdditionalData = { ...current.additionalData };
22+
// workspaceAutostartOptions needs to be overriden
23+
if (partialUser.additionalData?.workspaceAutostartOptions) {
24+
currentAdditionalData.workspaceAutostartOptions = [];
25+
}
26+
const update: UpdateCurrentUserArgs = {
27+
id: current.id,
28+
fullName: partialUser.fullName || current.fullName,
29+
additionalData: deepmerge<AdditionalUserData>(
30+
currentAdditionalData || {},
31+
partialUser.additionalData || {},
32+
),
33+
};
34+
const user = await getGitpodService().server.updateLoggedInUser(update);
35+
return converter.toUser(user);
1936
},
2037
});
2138
};
@@ -31,7 +48,6 @@ export const useUpdateCurrentUserDotfileRepoMutation = () => {
3148
}
3249

3350
const additionalData = {
34-
...(user.additionalData || {}),
3551
dotfileRepo,
3652
};
3753
const updatedUser = await updateUser.mutateAsync({ additionalData });
@@ -40,14 +56,14 @@ export const useUpdateCurrentUserDotfileRepoMutation = () => {
4056
},
4157
onMutate: async () => {
4258
return {
43-
previousDotfileRepo: user?.additionalData?.dotfileRepo || "",
59+
previousDotfileRepo: user?.dotfileRepo || "",
4460
};
4561
},
4662
onSuccess: (updatedUser, _, context) => {
47-
if (updatedUser?.additionalData?.dotfileRepo !== context?.previousDotfileRepo) {
63+
if (updatedUser?.dotfileRepo !== context?.previousDotfileRepo) {
4864
trackEvent("dotfile_repo_changed", {
4965
previous: context?.previousDotfileRepo ?? "",
50-
current: updatedUser?.additionalData?.dotfileRepo ?? "",
66+
current: updatedUser?.dotfileRepo ?? "",
5167
});
5268
}
5369
},

0 commit comments

Comments
 (0)