Skip to content

Commit

Permalink
GetAuthenticatedUser in Dashboard (#19142)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
AlexTugarev authored Dec 6, 2023
1 parent 0152b84 commit e4ccbf0
Show file tree
Hide file tree
Showing 58 changed files with 954 additions and 1,091 deletions.
1 change: 1 addition & 0 deletions components/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"countries-list": "^2.6.1",
"crypto-browserify": "3.12.0",
"dayjs": "^1.11.5",
"deepmerge": "^4.2.2",
"file-saver": "^2.0.5",
"idb-keyval": "^6.2.0",
"js-cookie": "^3.0.1",
Expand Down
78 changes: 40 additions & 38 deletions components/dashboard/src/AppNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
*/

import dayjs from "dayjs";
import deepMerge from "deepmerge";
import { useCallback, useEffect, useState } from "react";
import Alert, { AlertType } from "./components/Alert";
import { useUserLoader } from "./hooks/use-user-loader";
import { getGitpodService } from "./service/service";
import { isGitpodIo } from "./utils";
import { trackEvent } from "./Analytics";
import { useUpdateCurrentUserMutation } from "./data/current-user/update-mutation";
import { User as UserProtocol } from "@gitpod/gitpod-protocol";
import { User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";

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

const UPDATED_PRIVACY_POLICY: Notification = {
id: "privacy-policy-update",
type: "info",
preventDismiss: true,
onClose: async () => {
let dismissSuccess = false;
try {
const userUpdates = { additionalData: { profile: { acceptedPrivacyPolicyDate: dayjs().toISOString() } } };
const previousUser = await getGitpodService().server.getLoggedInUser();
const updatedUser = await getGitpodService().server.updateLoggedInUser(
deepMerge(previousUser, userUpdates),
);
dismissSuccess = !!updatedUser;
} catch (err) {
console.error("Failed to update user's privacy policy acceptance date", err);
dismissSuccess = false;
} finally {
trackEvent("privacy_policy_update_accepted", {
path: window.location.pathname,
success: dismissSuccess,
});
}
},
message: (
<span className="text-md">
We've updated our Privacy Policy. You can review it{" "}
<a className="gp-link" href="https://www.gitpod.io/privacy" target="_blank" rel="noreferrer">
here
</a>
.
</span>
),
const UPDATED_PRIVACY_POLICY = (updateUser: (user: Partial<UserProtocol>) => Promise<User>) => {
return {
id: "privacy-policy-update",
type: "info",
preventDismiss: true,
onClose: async () => {
let dismissSuccess = false;
try {
const updatedUser = await updateUser({
additionalData: { profile: { acceptedPrivacyPolicyDate: dayjs().toISOString() } },
});
dismissSuccess = !!updatedUser;
} catch (err) {
console.error("Failed to update user's privacy policy acceptance date", err);
dismissSuccess = false;
} finally {
trackEvent("privacy_policy_update_accepted", {
path: window.location.pathname,
success: dismissSuccess,
});
}
},
message: (
<span className="text-md">
We've updated our Privacy Policy. You can review it{" "}
<a className="gp-link" href="https://www.gitpod.io/privacy" target="_blank" rel="noreferrer">
here
</a>
.
</span>
),
} as Notification;
};

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

useEffect(() => {
const notifications = [];
if (!loading && isGitpodIo()) {
if (
!user?.additionalData?.profile?.acceptedPrivacyPolicyDate ||
new Date(PRIVACY_POLICY_LAST_UPDATED) > new Date(user.additionalData.profile.acceptedPrivacyPolicyDate)
!user?.profile?.acceptedPrivacyPolicyDate ||
new Date(PRIVACY_POLICY_LAST_UPDATED) > new Date(user.profile.acceptedPrivacyPolicyDate)
) {
notifications.push(UPDATED_PRIVACY_POLICY);
notifications.push(UPDATED_PRIVACY_POLICY((u: Partial<UserProtocol>) => updateUser.mutateAsync(u)));
}
}

const dismissedNotifications = getDismissedNotifications();
const topNotification = notifications.find((n) => !dismissedNotifications.includes(n.id));
setTopNotification(topNotification);
}, [loading, setTopNotification, user]);
}, [loading, updateUser, setTopNotification, user]);

const dismissNotification = useCallback(() => {
if (!topNotification) {
Expand Down
9 changes: 6 additions & 3 deletions components/dashboard/src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useNeedsSetup } from "./dedicated-setup/use-needs-setup";
import { AuthProviderDescription } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
import { Button, ButtonProps } from "@podkit/buttons/Button";
import { cn } from "@podkit/lib/cn";
import { userClient } from "./service/public-api";

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

const updateUser = useCallback(async () => {
await getGitpodService().reconnect();
const user = await getGitpodService().server.getLoggedInUser();
setUser(user);
markLoggedIn();
const { user } = await userClient.getAuthenticatedUser({});
if (user) {
setUser(user);
markLoggedIn();
}
}, [setUser]);

const authorizeSuccessful = useCallback(async () => {
Expand Down
3 changes: 2 additions & 1 deletion components/dashboard/src/admin/UserSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AdminPageHeader } from "./AdminPageHeader";
import UserDetail from "./UserDetail";
import searchIcon from "../icons/search.svg";
import Tooltip from "../components/Tooltip";
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";

export default function UserSearch() {
const location = useLocation();
Expand Down Expand Up @@ -129,7 +130,7 @@ function UserEntry(p: { user: User }) {
if (!p) {
return <></>;
}
const email = User.getPrimaryEmail(p.user) || "---";
const email = getPrimaryEmail(p.user) || "---";
return (
<Link key={p.user.id} to={"/admin/users/" + p.user.id} data-analytics='{"button_type":"sidebar_menu"}'>
<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">
Expand Down
3 changes: 2 additions & 1 deletion components/dashboard/src/app/AdminRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { useContext } from "react";
import { Redirect, Route } from "react-router";
import { UserContext } from "../user-context";
import { User_RoleOrPermission } from "@gitpod/public-api/lib/gitpod/v1/user_pb";

// A wrapper for <Route> that redirects to the workspaces screen if the user isn't a admin.
// This wrapper only accepts the component property
Expand All @@ -15,7 +16,7 @@ export function AdminRoute({ component }: any) {
return (
<Route
render={({ location }: any) =>
user?.rolesOrPermissions?.includes("admin") ? (
user?.rolesOrPermissions?.includes(User_RoleOrPermission.ADMIN) ? (
<Route component={component}></Route>
) : (
<Redirect
Expand Down
7 changes: 0 additions & 7 deletions components/dashboard/src/app/AppBlockingFlows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { FC, lazy } from "react";
import { useShowDedicatedSetup } from "../dedicated-setup/use-show-dedicated-setup";
import { useCurrentUser } from "../user-context";
import { MigrationPage, useShouldSeeMigrationPage } from "../whatsnew/MigrationPage";
import { useShowUserOnboarding } from "../onboarding/use-show-user-onboarding";
import { useHistory } from "react-router";
import { useCurrentOrg } from "../data/organizations/orgs-query";
Expand All @@ -22,7 +21,6 @@ export const AppBlockingFlows: FC = ({ children }) => {
const history = useHistory();
const user = useCurrentUser();
const org = useCurrentOrg();
const shouldSeeMigrationPage = useShouldSeeMigrationPage();
const showDedicatedSetup = useShowDedicatedSetup();
const showUserOnboarding = useShowUserOnboarding();

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

// If orgOnlyAttribution is enabled and the user hasn't been migrated, yet, we need to show the migration page
if (shouldSeeMigrationPage) {
return <MigrationPage />;
}

// Handle dedicated setup if necessary
if (showDedicatedSetup.showSetup) {
return (
Expand Down
10 changes: 1 addition & 9 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License.AGPL.txt in the project root for license information.
*/

import React, { useState } from "react";
import React from "react";
import { Redirect, Route, Switch, useLocation } from "react-router";
import OAuthClientApproval from "../OauthClientApproval";
import Menu from "../menu/Menu";
Expand All @@ -25,15 +25,13 @@ import {
usagePathMain,
} from "../user-settings/settings.routes";
import { getURLHash, isGitpodIo } from "../utils";
import { WhatsNew, shouldSeeWhatsNew } from "../whatsnew/WhatsNew";
import { workspacesPathMain } from "../workspaces/workspaces.routes";
import { AdminRoute } from "./AdminRoute";
import { Blocked } from "./Blocked";

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

export const AppRoutes = () => {
const hash = getURLHash();
const user = useCurrentUser();
const [isWhatsNewShown, setWhatsNewShown] = useState(user && shouldSeeWhatsNew(user));
const location = useLocation();
const repoConfigListAndDetail = useFeatureFlag("repoConfigListAndDetail");

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

if (isWhatsNewShown) {
return <WhatsNew onClose={() => setWhatsNewShown(false)} />;
}

// TODO: Try and encapsulate this in a route for "/" (check for hash in route component, render or redirect accordingly)
const isCreation = location.pathname === "/" && hash !== "";
if (isCreation) {
Expand Down
15 changes: 9 additions & 6 deletions components/dashboard/src/components/AuthorizeGit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { FC, useCallback, useContext } from "react";
import { Link } from "react-router-dom";
import { useAuthProviderDescriptions } from "../data/auth-providers/auth-provider-descriptions-query";
import { openAuthorizeWindow } from "../provider-utils";
import { getGitpodService } from "../service/service";
import { userClient } from "../service/public-api";
import { UserContext, useCurrentUser } from "../user-context";
import { Button } from "./Button";
import { Heading2, Heading3, Subheading } from "./typography/headings";
Expand All @@ -18,20 +18,23 @@ import { useIsOwner } from "../data/organizations/members-query";
import { AuthProviderDescription } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";

export function useNeedsGitAuthorization() {
const authProviders = useAuthProviderDescriptions();
const { data: authProviders } = useAuthProviderDescriptions();
const user = useCurrentUser();
if (!user || !authProviders.data) {
if (!user || !authProviders) {
return false;
}
return !authProviders.data.some((ap) => user.identities.some((i) => ap.id === i.authProviderId));
return !authProviders.some((ap) => user.identities.some((i) => ap.id === i.authProviderId));
}

export const AuthorizeGit: FC<{ className?: string }> = ({ className }) => {
const { setUser } = useContext(UserContext);
const owner = useIsOwner();
const { data: authProviders } = useAuthProviderDescriptions();
const updateUser = useCallback(() => {
getGitpodService().server.getLoggedInUser().then(setUser);
const updateUser = useCallback(async () => {
const response = await userClient.getAuthenticatedUser({});
if (response.user) {
setUser(response.user);
}
}, [setUser]);

const connect = useCallback(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2023 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 { useQuery } from "@tanstack/react-query";
import { userClient } from "../../service/public-api";
import { GetAuthenticatedUserRequest, User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";

export const useAuthenticatedUser = () => {
const query = useQuery<User>({
queryKey: getAuthenticatedUserQueryKey(),
queryFn: async () => {
const params = new GetAuthenticatedUserRequest();
const response = await userClient.getAuthenticatedUser(params);
return response.user!;
},
});
return query;
};

export const getAuthenticatedUserQueryKey = () => ["authenticated-user", {}];
30 changes: 23 additions & 7 deletions components/dashboard/src/data/current-user/update-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,35 @@
* See License.AGPL.txt in the project root for license information.
*/

import { User } from "@gitpod/gitpod-protocol";
import { AdditionalUserData, User as UserProtocol } from "@gitpod/gitpod-protocol";
import { useMutation } from "@tanstack/react-query";
import { trackEvent } from "../../Analytics";
import { getGitpodService } from "../../service/service";
import { useCurrentUser } from "../../user-context";
import { converter } from "../../service/public-api";
import deepmerge from "deepmerge";

type UpdateCurrentUserArgs = Partial<User>;
type UpdateCurrentUserArgs = Partial<UserProtocol>;

export const useUpdateCurrentUserMutation = () => {
return useMutation({
mutationFn: async (partialUser: UpdateCurrentUserArgs) => {
return await getGitpodService().server.updateLoggedInUser(partialUser);
const current = await getGitpodService().server.getLoggedInUser();
const currentAdditionalData = { ...current.additionalData };
// workspaceAutostartOptions needs to be overriden
if (partialUser.additionalData?.workspaceAutostartOptions) {
currentAdditionalData.workspaceAutostartOptions = [];
}
const update: UpdateCurrentUserArgs = {
id: current.id,
fullName: partialUser.fullName || current.fullName,
additionalData: deepmerge<AdditionalUserData>(
currentAdditionalData || {},
partialUser.additionalData || {},
),
};
const user = await getGitpodService().server.updateLoggedInUser(update);
return converter.toUser(user);
},
});
};
Expand All @@ -31,7 +48,6 @@ export const useUpdateCurrentUserDotfileRepoMutation = () => {
}

const additionalData = {
...(user.additionalData || {}),
dotfileRepo,
};
const updatedUser = await updateUser.mutateAsync({ additionalData });
Expand All @@ -40,14 +56,14 @@ export const useUpdateCurrentUserDotfileRepoMutation = () => {
},
onMutate: async () => {
return {
previousDotfileRepo: user?.additionalData?.dotfileRepo || "",
previousDotfileRepo: user?.dotfileRepo || "",
};
},
onSuccess: (updatedUser, _, context) => {
if (updatedUser?.additionalData?.dotfileRepo !== context?.previousDotfileRepo) {
if (updatedUser?.dotfileRepo !== context?.previousDotfileRepo) {
trackEvent("dotfile_repo_changed", {
previous: context?.previousDotfileRepo ?? "",
current: updatedUser?.additionalData?.dotfileRepo ?? "",
current: updatedUser?.dotfileRepo ?? "",
});
}
},
Expand Down
6 changes: 5 additions & 1 deletion components/dashboard/src/data/featureflag-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* See License.AGPL.txt in the project root for license information.
*/

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

const query = useQuery(queryKey, async () => {
const flagValue = await getExperimentsClient().getValueAsync(featureFlag, featureFlags[featureFlag], {
user,
user: user && {
id: user.id,
email: getPrimaryEmail(user),
},
projectId: project?.id,
teamId: org?.id,
teamName: org?.name,
Expand Down
Loading

0 comments on commit e4ccbf0

Please sign in to comment.