From 6dd745dd989d73ae2f03b6cc0fc708b13a403329 Mon Sep 17 00:00:00 2001 From: Alex Tugarev Date: Mon, 27 Nov 2023 11:25:53 +0000 Subject: [PATCH 01/13] [dashboard] remove unused service mock --- .../dashboard/src/service/service-mock.ts | 252 ------------------ components/dashboard/src/service/service.tsx | 4 - 2 files changed, 256 deletions(-) delete mode 100644 components/dashboard/src/service/service-mock.ts diff --git a/components/dashboard/src/service/service-mock.ts b/components/dashboard/src/service/service-mock.ts deleted file mode 100644 index 5596eae3fd5f1c..00000000000000 --- a/components/dashboard/src/service/service-mock.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * 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 { createServiceMock, Event, Project, Team, User } from "@gitpod/gitpod-protocol"; - -const u1: User = { - id: "1234", - creationDate: "2018-05-01T07:00:00.000Z", - avatarUrl: "https://avatars.githubusercontent.com/u/10137?v=4", - name: "gp-test", - fullName: "Alex", - identities: [ - { - authProviderId: "Public-GitHub", - authId: "1234", - authName: "GitpodTester", - primaryEmail: "tester@gitpod.io", - }, - ], - rolesOrPermissions: [], - additionalData: { - whatsNewSeen: { - "April-2021": "true", - "June-2021": "true", - }, - emailNotificationSettings: { - allowsChangelogMail: true, - allowsDevXMail: true, - }, - }, -}; -const t1 = new Date(Date.now() - 123533).toISOString(); -const team1: Team = { - id: "team1", - name: "ACME", - creationTime: t1, -}; -const pr1: Project = { - appInstallationId: "app1", - cloneUrl: "https://github.com/AlexTugarev/txt.git", - creationTime: t1, - id: "pr1", - name: "TXT", - teamId: "team1", -}; -const gitpodServiceMock = createServiceMock({ - getLoggedInUser: async () => { - return u1; - }, - updateLoggedInUser: async (user: User) => { - for (const attribute in user) { - // @ts-ignore - u1[attribute] = user[attribute]; - } - return u1; - }, - getTeams: async () => { - return [team1]; - }, - getTeamMembers: async (teamId) => { - return [ - { - memberSince: t1, - role: "owner", - userId: u1.id, - avatarUrl: u1.avatarUrl, - fullName: u1.fullName, - primaryEmail: "alex@gitpod.io", - ownedByOrganization: false, - }, - ]; - }, - getGenericInvite: async () => { - return { - id: "000", - creationTime: t1, - invalidationTime: t1, - role: "member", - teamId: "team1", - }; - }, - getTeamProjects: async () => { - return [pr1]; - }, - getProjectOverview: async () => { - return { - branches: [ - { - name: "main", - url: "branchUrl", - changeDate: t1, - changeAuthor: u1.fullName!, - changeAuthorAvatar: u1.avatarUrl, - changeHash: "2C0FFE", - changeTitle: "[Comp] Add new functionality for", - isDefault: true, - status: "available", - }, - ], - }; - }, - findPrebuilds: async (p) => { - const { projectId } = p; - return [ - { - info: { - id: "pb1", - branch: "main", - buildWorkspaceId: "123", - teamId: "t1", - projectId, - projectName: "pb1", - cloneUrl: pr1.cloneUrl, - startedAt: t1, - startedBy: u1.id, - startedByAvatar: u1.avatarUrl, - changeTitle: "[Comp] Add new functionality for", - changeDate: t1, - changeAuthor: u1.fullName!, - changeAuthorAvatar: u1.avatarUrl, - changePR: "4647", - changeUrl: "https://github.com/gitpod-io/gitpod/pull/4738", - changeHash: "2C0FFE", - }, - status: "available", - }, - { - info: { - id: "pb1", - branch: "foo/bar", - buildWorkspaceId: "1234", - teamId: "t1", - projectId, - projectName: "pb1", - cloneUrl: pr1.cloneUrl, - startedAt: t1, - startedBy: u1.id, - startedByAvatar: u1.avatarUrl, - changeTitle: "Fix Bug Nr 1", - changeDate: t1, - changeAuthor: u1.fullName!, - changeAuthorAvatar: u1.avatarUrl, - changePR: "4245", - changeUrl: "https://github.com/gitpod-io/gitpod/pull/4738", - changeHash: "1C0FFE", - }, - status: "available", - }, - ]; - }, - getProviderRepositoriesForUser: async () => { - return []; - }, - getWorkspaces: async () => { - return []; - }, - getAuthProviders: async () => { - return [ - { - authProviderId: "Public-GitHub", - authProviderType: "GitHub", - verified: true, - host: "github.com", - icon: "", - description: "", - isReadonly: false, - }, - { - authProviderId: "Public-GitLab", - authProviderType: "GitLab", - verified: true, - host: "gitlab.com", - icon: "", - description: "", - isReadonly: false, - }, - ]; - }, - getOwnAuthProviders: async () => { - return [ - { - id: "foobar123", - ownerId: "1234", - status: "verified", - host: "testing.doptig.com/gitlab", - type: "GitLab", - oauth: { - authorizationUrl: "https://testing.doptig.com/gitlab/oauth/authorize", - tokenUrl: "https://testing.doptig.com/gitlab/oauth/token", - settingsUrl: "https://testing.doptig.com/gitlab/-/profile/applications", - callBackUrl: "https://gitpod-staging.com/auth/testing.doptig.com/gitlab/callback", - clientId: "clientid-123", - clientSecret: "redacted", - }, - oauthRevision: "some-revision", - deleted: false, - }, - ]; - }, - onDidOpenConnection: Event.None, - onDidCloseConnection: Event.None, - trackEvent: async (event) => {}, - trackLocation: async (event) => {}, - getSupportedWorkspaceClasses: async () => { - return [ - { - id: "g1-standard", - category: "GENERAL PURPOSE", - displayName: "Standard", - description: "Up to 4 cores, 8GB RAM, 30GB storage", - powerups: 1, - isDefault: true, - }, - { - id: "g1-large", - category: "GENERAL PURPOSE", - displayName: "Large", - description: "Up to 8 cores, 16GB RAM, 50GB storage", - powerups: 2, - isDefault: false, - }, - ]; - }, - getClientRegion: async () => { - return "europe-west-1"; - }, - getIDEOptions: async () => { - return { - defaultDesktopIde: "code-desktop", - defaultIde: "code", - options: { - code: { - title: "VS Code", - type: "browser", - logo: "", - image: "eu.gcr.io/gitpod-core-dev/build/ide/code:commit-050c611f28564c6c7b1e58db470f07997dfb4730", - }, - "code-desktop": { - title: "VS Code", - type: "desktop", - logo: "", - image: "eu.gcr.io/gitpod-core-dev/build/ide/code-desktop:commit-9b29fc94cc1f0c776ef74f60dc3a7ce68d41bdbe", - }, - }, - }; - }, -}); - -export { gitpodServiceMock }; diff --git a/components/dashboard/src/service/service.tsx b/components/dashboard/src/service/service.tsx index 960afd97dda339..b02d870d60dfd1 100644 --- a/components/dashboard/src/service/service.tsx +++ b/components/dashboard/src/service/service.tsx @@ -75,10 +75,6 @@ function instrumentWebSocketConnection(connectionProvider: WebSocketConnectionPr export function getGitpodService(): GitpodService { const w = window as any; const _gp = w._gp || (w._gp = {}); - if (window.location.search.includes("service=mock")) { - const service = _gp.gitpodService || (_gp.gitpodService = require("./service-mock").gitpodServiceMock); - return service; - } let service = _gp.gitpodService; if (!service) { service = _gp.gitpodService = createGitpodService(); From ab374616f644be3179606088d91517f66ec8f4de Mon Sep 17 00:00:00 2001 From: Alex Tugarev Date: Mon, 27 Nov 2023 11:20:08 +0000 Subject: [PATCH 02/13] [dashboard] use `GetAuthenticatedUser` instead of `getLoggedInUser` --- components/dashboard/package.json | 1 + components/dashboard/src/AppNotifications.tsx | 78 +++---- components/dashboard/src/Login.tsx | 9 +- components/dashboard/src/app/AdminRoute.tsx | 3 +- .../dashboard/src/app/AppBlockingFlows.tsx | 7 - components/dashboard/src/app/AppRoutes.tsx | 10 +- .../dashboard/src/components/AuthorizeGit.tsx | 15 +- .../current-user/authenticated-user-query.ts | 23 ++ .../src/data/current-user/update-mutation.ts | 25 ++- .../src/data/organizations/orgs-query.ts | 2 +- components/dashboard/src/data/setup.tsx | 4 +- .../src/dedicated-setup/DedicatedSetup.tsx | 9 +- .../src/hooks/use-analytics-tracking.ts | 2 +- .../dashboard/src/hooks/use-user-loader.ts | 8 +- components/dashboard/src/menu/Menu.tsx | 9 +- .../src/menu/OrganizationSelector.tsx | 6 +- .../dashboard/src/onboarding/StepOrgInfo.tsx | 26 +-- .../src/onboarding/StepPersonalize.tsx | 6 +- .../dashboard/src/onboarding/StepUserInfo.tsx | 15 +- .../src/onboarding/UserOnboarding.tsx | 11 +- .../onboarding/use-show-user-onboarding.ts | 6 +- .../src/service/json-rpc-user-client.ts | 42 ++++ .../dashboard/src/service/public-api.ts | 13 +- components/dashboard/src/service/service.tsx | 6 +- .../git-integrations/GitIntegrationModal.tsx | 10 +- components/dashboard/src/user-context.tsx | 7 +- .../dashboard/src/user-settings/Account.tsx | 30 ++- .../src/user-settings/Integrations.tsx | 14 +- .../src/user-settings/Notifications.tsx | 43 ++-- .../src/user-settings/Preferences.tsx | 48 +++-- .../dashboard/src/user-settings/SelectIDE.tsx | 43 ++-- .../dashboard/src/whatsnew/MigrationPage.tsx | 139 ------------ .../src/whatsnew/WhatsNew-2021-04.tsx | 78 ------- .../src/whatsnew/WhatsNew-2021-06.tsx | 47 ---- .../dashboard/src/whatsnew/WhatsNew.tsx | 102 --------- .../src/workspaces/CreateWorkspacePage.tsx | 58 ++--- components/gitpod-protocol/src/protocol.ts | 19 +- .../fixtures/toUser_1.golden | 75 +++++++ .../typescript-common/fixtures/toUser_1.json | 84 ++++++++ .../public-api/typescript-common/package.json | 3 +- .../src/public-api-converter.spec.ts | 4 + .../src/public-api-converter.ts | 204 ++++++++++++++++-- .../typescript-common/src/user-utils.ts | 99 +++++++++ 43 files changed, 782 insertions(+), 661 deletions(-) create mode 100644 components/dashboard/src/data/current-user/authenticated-user-query.ts create mode 100644 components/dashboard/src/service/json-rpc-user-client.ts delete mode 100644 components/dashboard/src/whatsnew/MigrationPage.tsx delete mode 100644 components/dashboard/src/whatsnew/WhatsNew-2021-04.tsx delete mode 100644 components/dashboard/src/whatsnew/WhatsNew-2021-06.tsx delete mode 100644 components/dashboard/src/whatsnew/WhatsNew.tsx create mode 100644 components/public-api/typescript-common/fixtures/toUser_1.golden create mode 100644 components/public-api/typescript-common/fixtures/toUser_1.json create mode 100644 components/public-api/typescript-common/src/user-utils.ts diff --git a/components/dashboard/package.json b/components/dashboard/package.json index 1534d33b5a40af..4d3d5497481f46 100644 --- a/components/dashboard/package.json +++ b/components/dashboard/package.json @@ -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", diff --git a/components/dashboard/src/AppNotifications.tsx b/components/dashboard/src/AppNotifications.tsx index ce6e588eaef39a..d24f2e7754c0ab 100644 --- a/components/dashboard/src/AppNotifications.tsx +++ b/components/dashboard/src/AppNotifications.tsx @@ -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"; @@ -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: ( - - We've updated our Privacy Policy. You can review it{" "} - - here - - . - - ), +const UPDATED_PRIVACY_POLICY = (updateUser: (user: Partial) => Promise) => { + 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: ( + + We've updated our Privacy Policy. You can review it{" "} + + here + + . + + ), + } as Notification; }; export function AppNotifications() { const [topNotification, setTopNotification] = useState(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) => 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) { diff --git a/components/dashboard/src/Login.tsx b/components/dashboard/src/Login.tsx index f8eaa99b135d30..f0ea3be4c25937 100644 --- a/components/dashboard/src/Login.tsx +++ b/components/dashboard/src/Login.tsx @@ -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); @@ -67,9 +68,11 @@ export const Login: FC = ({ 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 () => { diff --git a/components/dashboard/src/app/AdminRoute.tsx b/components/dashboard/src/app/AdminRoute.tsx index 8ee6143ef2a68b..611f4e1360f3c9 100644 --- a/components/dashboard/src/app/AdminRoute.tsx +++ b/components/dashboard/src/app/AdminRoute.tsx @@ -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 that redirects to the workspaces screen if the user isn't a admin. // This wrapper only accepts the component property @@ -15,7 +16,7 @@ export function AdminRoute({ component }: any) { return ( - user?.rolesOrPermissions?.includes("admin") ? ( + user?.rolesOrPermissions?.includes(User_RoleOrPermission.ADMIN) ? ( ) : ( { const history = useHistory(); const user = useCurrentUser(); const org = useCurrentOrg(); - const shouldSeeMigrationPage = useShouldSeeMigrationPage(); const showDedicatedSetup = useShowDedicatedSetup(); const showUserOnboarding = useShowUserOnboarding(); @@ -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 ; - } - // Handle dedicated setup if necessary if (showDedicatedSetup.showSetup) { return ( diff --git a/components/dashboard/src/app/AppRoutes.tsx b/components/dashboard/src/app/AppRoutes.tsx index 726f071bb65470..033228b6f4027f 100644 --- a/components/dashboard/src/app/AppRoutes.tsx +++ b/components/dashboard/src/app/AppRoutes.tsx @@ -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"; @@ -25,7 +25,6 @@ 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"; @@ -33,7 +32,6 @@ 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"; @@ -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"); @@ -99,10 +95,6 @@ export const AppRoutes = () => { return ; } - if (isWhatsNewShown) { - return 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) { diff --git a/components/dashboard/src/components/AuthorizeGit.tsx b/components/dashboard/src/components/AuthorizeGit.tsx index aa8ad0c0cd1640..f807a7720740e1 100644 --- a/components/dashboard/src/components/AuthorizeGit.tsx +++ b/components/dashboard/src/components/AuthorizeGit.tsx @@ -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"; @@ -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( diff --git a/components/dashboard/src/data/current-user/authenticated-user-query.ts b/components/dashboard/src/data/current-user/authenticated-user-query.ts new file mode 100644 index 00000000000000..f9fde8b45e28fe --- /dev/null +++ b/components/dashboard/src/data/current-user/authenticated-user-query.ts @@ -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({ + queryKey: getAuthenticatedUserQueryKey(), + queryFn: async () => { + const params = new GetAuthenticatedUserRequest(); + const response = await userClient.getAuthenticatedUser(params); + return response.user!; + }, + }); + return query; +}; + +export const getAuthenticatedUserQueryKey = () => ["authenticated-user", {}]; diff --git a/components/dashboard/src/data/current-user/update-mutation.ts b/components/dashboard/src/data/current-user/update-mutation.ts index ec84db5d72b0db..1ed9443d08b038 100644 --- a/components/dashboard/src/data/current-user/update-mutation.ts +++ b/components/dashboard/src/data/current-user/update-mutation.ts @@ -4,18 +4,30 @@ * 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; +type UpdateCurrentUserArgs = Partial; export const useUpdateCurrentUserMutation = () => { return useMutation({ mutationFn: async (partialUser: UpdateCurrentUserArgs) => { - return await getGitpodService().server.updateLoggedInUser(partialUser); + const current = await getGitpodService().server.getLoggedInUser(); + const update: UpdateCurrentUserArgs = { + id: current.id, + fullName: partialUser.fullName || current.fullName, + additionalData: deepmerge( + current.additionalData || {}, + partialUser.additionalData || {}, + ), + }; + const user = await getGitpodService().server.updateLoggedInUser(update); + return converter.toUser(user); }, }); }; @@ -31,7 +43,6 @@ export const useUpdateCurrentUserDotfileRepoMutation = () => { } const additionalData = { - ...(user.additionalData || {}), dotfileRepo, }; const updatedUser = await updateUser.mutateAsync({ additionalData }); @@ -40,14 +51,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 ?? "", }); } }, diff --git a/components/dashboard/src/data/organizations/orgs-query.ts b/components/dashboard/src/data/organizations/orgs-query.ts index 45bb77d85c3d98..7784e7a9fbd055 100644 --- a/components/dashboard/src/data/organizations/orgs-query.ts +++ b/components/dashboard/src/data/organizations/orgs-query.ts @@ -4,7 +4,7 @@ * See License.AGPL.txt in the project root for license information. */ -import { User } from "@gitpod/gitpod-protocol"; +import { User } from "@gitpod/public-api/lib/gitpod/v1/user_pb"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; import { useLocation } from "react-router"; diff --git a/components/dashboard/src/data/setup.tsx b/components/dashboard/src/data/setup.tsx index 2ab1b5ac4296b7..a9b8f06a28132c 100644 --- a/components/dashboard/src/data/setup.tsx +++ b/components/dashboard/src/data/setup.tsx @@ -28,11 +28,12 @@ import * as VerificationClasses from "@gitpod/public-api/lib/gitpod/v1/verificat import * as InstallationClasses from "@gitpod/public-api/lib/gitpod/v1/installation_pb"; import * as SCMClasses from "@gitpod/public-api/lib/gitpod/v1/scm_pb"; import * as SSHClasses from "@gitpod/public-api/lib/gitpod/v1/ssh_pb"; +import * as UserClasses from "@gitpod/public-api/lib/gitpod/v1/user_pb"; // This is used to version the cache // If data we cache changes in a non-backwards compatible way, increment this version // That will bust any previous cache versions a client may have stored -const CACHE_VERSION = "16"; +const CACHE_VERSION = "17"; export function noPersistence(queryKey: QueryKey): QueryKey { return [...queryKey, "no-persistence"]; @@ -158,6 +159,7 @@ function initializeMessages() { ...Object.values(InstallationClasses), ...Object.values(SCMClasses), ...Object.values(SSHClasses), + ...Object.values(UserClasses), ]; for (const c of constr) { if ((c as any).prototype instanceof Message) { diff --git a/components/dashboard/src/dedicated-setup/DedicatedSetup.tsx b/components/dashboard/src/dedicated-setup/DedicatedSetup.tsx index 38d9c8aa646273..44fdd8f4694afd 100644 --- a/components/dashboard/src/dedicated-setup/DedicatedSetup.tsx +++ b/components/dashboard/src/dedicated-setup/DedicatedSetup.tsx @@ -21,6 +21,7 @@ import { useDocumentTitle } from "../hooks/use-document-title"; import { forceDedicatedSetupParam } from "./use-show-dedicated-setup"; import { Organization } from "@gitpod/public-api/lib/gitpod/v1/organization_pb"; import { Delayed } from "@podkit/loading/Delayed"; +import { userClient } from "../service/public-api"; type Props = { onComplete: () => void; @@ -96,9 +97,13 @@ const DedicatedSetupSteps: FC = ({ org, ssoConfig, onC }, [dropConfetti]); const updateUser = useCallback(async () => { + // TODO(at) this is still required if the FE shim is used per FF await getGitpodService().reconnect(); - const user = await getGitpodService().server.getLoggedInUser(); - setUser(user); + + const response = await userClient.getAuthenticatedUser({}); + if (response.user) { + setUser(response.user); + } }, [setUser]); const handleEndSetup = useCallback(async () => { diff --git a/components/dashboard/src/hooks/use-analytics-tracking.ts b/components/dashboard/src/hooks/use-analytics-tracking.ts index 20d0cfd5abafc5..f0ae2a917c3c81 100644 --- a/components/dashboard/src/hooks/use-analytics-tracking.ts +++ b/components/dashboard/src/hooks/use-analytics-tracking.ts @@ -59,7 +59,7 @@ export const useAnalyticsTracking = () => { }, []); useEffect(() => { - if (!user || !user.additionalData?.profile?.onboardedTimestamp || !isOrbitalLoaded) { + if (!user || !user.profile?.onboardedTimestamp || !isOrbitalLoaded) { return; } diff --git a/components/dashboard/src/hooks/use-user-loader.ts b/components/dashboard/src/hooks/use-user-loader.ts index a6ad82e91bb7ce..a6cb6c83eb7bc6 100644 --- a/components/dashboard/src/hooks/use-user-loader.ts +++ b/components/dashboard/src/hooks/use-user-loader.ts @@ -6,12 +6,12 @@ import { useContext } from "react"; import { UserContext } from "../user-context"; -import { getGitpodService } from "../service/service"; import { trackLocation } from "../Analytics"; import { useQuery } from "@tanstack/react-query"; import { noPersistence } from "../data/setup"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { useFeatureFlag } from "../data/featureflag-query"; +import { userClient } from "../service/public-api"; export const useUserLoader = () => { const { user, setUser } = useContext(UserContext); @@ -22,7 +22,7 @@ export const useUserLoader = () => { const { isLoading } = useQuery({ queryKey: noPersistence(["current-user"]), queryFn: async () => { - const user = await getGitpodService().server.getLoggedInUser(); + const user = (await userClient.getAuthenticatedUser({})).user; return user || null; }, @@ -41,7 +41,9 @@ export const useUserLoader = () => { cacheTime: 1000 * 60 * 60 * 1, // 1 hour staleTime: 1000 * 60 * 60 * 1, // 1 hour onSuccess: (loadedUser) => { - setUser(loadedUser); + if (loadedUser) { + setUser(loadedUser); + } }, onSettled: (loadedUser) => { trackLocation(!!loadedUser); diff --git a/components/dashboard/src/menu/Menu.tsx b/components/dashboard/src/menu/Menu.tsx index ca391170b21ddf..73e9b9df0e8bb1 100644 --- a/components/dashboard/src/menu/Menu.tsx +++ b/components/dashboard/src/menu/Menu.tsx @@ -4,7 +4,6 @@ * See License.AGPL.txt in the project root for license information. */ -import { User } from "@gitpod/gitpod-protocol"; import { FC, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { useLocation } from "react-router"; import { Location } from "history"; @@ -20,6 +19,8 @@ import { isGitpodIo } from "../utils"; import OrganizationSelector from "./OrganizationSelector"; import { getAdminTabs } from "../admin/admin.routes"; import classNames from "classnames"; +import { User, User_RoleOrPermission } from "@gitpod/public-api/lib/gitpod/v1/user_pb"; +import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils"; interface Entry { title: string; @@ -82,7 +83,7 @@ export default function Menu() {