From 9dd4269363b0d2cca9f822ec2d90bbdfda891c38 Mon Sep 17 00:00:00 2001 From: Milan Pavlik Date: Thu, 24 Nov 2022 14:05:51 +0000 Subject: [PATCH] [dashboard] List projects using Public API --- components/dashboard/src/Menu.tsx | 16 ++++-- .../src/contexts/FeatureFlagContext.tsx | 5 ++ components/dashboard/src/projects/Events.tsx | 18 +++++- .../dashboard/src/projects/Prebuild.tsx | 20 +++++-- .../dashboard/src/projects/Prebuilds.tsx | 19 +++++-- components/dashboard/src/projects/Project.tsx | 19 +++++-- .../dashboard/src/projects/Projects.tsx | 19 ++++++- .../dashboard/src/service/public-api.ts | 57 ++++++++++++++++++- .../gitpod-protocol/go/gitpod-service.go | 6 +- 9 files changed, 153 insertions(+), 26 deletions(-) diff --git a/components/dashboard/src/Menu.tsx b/components/dashboard/src/Menu.tsx index 426f12ddf5c47a..dda856bee8e9bb 100644 --- a/components/dashboard/src/Menu.tsx +++ b/components/dashboard/src/Menu.tsx @@ -28,6 +28,7 @@ import { inResource, isGitpodIo } from "./utils"; import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode"; import { FeatureFlagContext } from "./contexts/FeatureFlagContext"; import { publicApiTeamMembersToProtocol, teamsService } from "./service/public-api"; +import { listAllProjects } from "./service/public-api"; interface Entry { title: string; @@ -37,7 +38,7 @@ interface Entry { export default function Menu() { const { user } = useContext(UserContext); - const { showUsageView, usePublicApiTeamsService } = useContext(FeatureFlagContext); + const { showUsageView, usePublicApiTeamsService, usePublicApiProjectsService } = useContext(FeatureFlagContext); const { teams } = useContext(TeamsContext); const location = useLocation(); const team = getCurrentTeam(location, teams); @@ -154,9 +155,16 @@ export default function Menu() { return; } (async () => { - const projects = !!team - ? await getGitpodService().server.getTeamProjects(team.id) - : await getGitpodService().server.getUserProjects(); + let projects: Project[]; + if (!!team) { + projects = usePublicApiProjectsService + ? await listAllProjects({ teamId: team.id }) + : await getGitpodService().server.getTeamProjects(team.id); + } else { + projects = usePublicApiProjectsService + ? await listAllProjects({ userId: user?.id }) + : await getGitpodService().server.getUserProjects(); + } // Find project matching with slug, otherwise with name const project = diff --git a/components/dashboard/src/contexts/FeatureFlagContext.tsx b/components/dashboard/src/contexts/FeatureFlagContext.tsx index 61f16c91b05da4..75ef18b49851e9 100644 --- a/components/dashboard/src/contexts/FeatureFlagContext.tsx +++ b/components/dashboard/src/contexts/FeatureFlagContext.tsx @@ -20,12 +20,14 @@ const FeatureFlagContext = createContext<{ isUsageBasedBillingEnabled: boolean; showUseLastSuccessfulPrebuild: boolean; usePublicApiTeamsService: boolean; + usePublicApiProjectsService: boolean; enablePersonalAccessTokens: boolean; }>({ showUsageView: false, isUsageBasedBillingEnabled: false, showUseLastSuccessfulPrebuild: false, usePublicApiTeamsService: false, + usePublicApiProjectsService: false, enablePersonalAccessTokens: false, }); @@ -39,6 +41,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { const [isUsageBasedBillingEnabled, setIsUsageBasedBillingEnabled] = useState(false); const [showUseLastSuccessfulPrebuild, setShowUseLastSuccessfulPrebuild] = useState(false); const [usePublicApiTeamsService, setUsePublicApiTeamsService] = useState(false); + const [usePublicApiProjectsService, setUsePublicApiProjectsService] = useState(false); const [enablePersonalAccessTokens, setPersonalAccessTokensEnabled] = useState(false); useEffect(() => { @@ -49,6 +52,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { isUsageBasedBillingEnabled: { defaultValue: false, setter: setIsUsageBasedBillingEnabled }, showUseLastSuccessfulPrebuild: { defaultValue: false, setter: setShowUseLastSuccessfulPrebuild }, publicApiExperimentalTeamsService: { defaultValue: false, setter: setUsePublicApiTeamsService }, + publicApiExperimentalProjectsService: { defaultValue: false, setter: setUsePublicApiProjectsService }, personalAccessTokensEnabled: { defaultValue: false, setter: setPersonalAccessTokensEnabled }, }; @@ -94,6 +98,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { showUseLastSuccessfulPrebuild, usePublicApiTeamsService, enablePersonalAccessTokens, + usePublicApiProjectsService, }} > {children} diff --git a/components/dashboard/src/projects/Events.tsx b/components/dashboard/src/projects/Events.tsx index b039396f0abb05..fe577c90ec7ef2 100644 --- a/components/dashboard/src/projects/Events.tsx +++ b/components/dashboard/src/projects/Events.tsx @@ -17,11 +17,16 @@ import Spinner from "../icons/Spinner.svg"; import NoAccess from "../icons/NoAccess.svg"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { openAuthorizeWindow } from "../provider-utils"; +import { UserContext } from "../user-context"; +import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; +import { listAllProjects } from "../service/public-api"; export default function () { const location = useLocation(); const { teams } = useContext(TeamsContext); + const { user } = useContext(UserContext); + const { usePublicApiProjectsService } = useContext(FeatureFlagContext); const team = getCurrentTeam(location, teams); const match = useRouteMatch<{ team: string; resource: string }>("/(t/)?:team/:resource"); @@ -61,9 +66,16 @@ export default function () { if (!teams || !projectSlug) { return; } - const projects = !!team - ? await getGitpodService().server.getTeamProjects(team.id) - : await getGitpodService().server.getUserProjects(); + let projects: Project[]; + if (!!team) { + projects = usePublicApiProjectsService + ? await listAllProjects({ teamId: team.id }) + : await getGitpodService().server.getTeamProjects(team.id); + } else { + projects = usePublicApiProjectsService + ? await listAllProjects({ userId: user?.id }) + : await getGitpodService().server.getUserProjects(); + } // Find project matching with slug, otherwise with name const project = projectSlug && projects.find((p) => (p.slug ? p.slug === projectSlug : p.name === projectSlug)); diff --git a/components/dashboard/src/projects/Prebuild.tsx b/components/dashboard/src/projects/Prebuild.tsx index e46ad41e96f455..88207812e86fe5 100644 --- a/components/dashboard/src/projects/Prebuild.tsx +++ b/components/dashboard/src/projects/Prebuild.tsx @@ -5,7 +5,7 @@ */ import dayjs from "dayjs"; -import { PrebuildWithStatus } from "@gitpod/gitpod-protocol"; +import { PrebuildWithStatus, Project } from "@gitpod/gitpod-protocol"; import { useContext, useEffect, useState } from "react"; import { useHistory, useLocation, useRouteMatch } from "react-router"; import Header from "../components/Header"; @@ -14,12 +14,17 @@ import Spinner from "../icons/Spinner.svg"; import { getGitpodService, gitpodHostUrl } from "../service/service"; import { TeamsContext, getCurrentTeam } from "../teams/teams-context"; import { shortCommitMessage } from "./render-utils"; +import { listAllProjects } from "../service/public-api"; +import { UserContext } from "../user-context"; +import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; export default function () { const history = useHistory(); const location = useLocation(); const { teams } = useContext(TeamsContext); + const { user } = useContext(UserContext); + const { usePublicApiProjectsService } = useContext(FeatureFlagContext); const team = getCurrentTeam(location, teams); const match = useRouteMatch<{ team: string; project: string; prebuildId: string }>( @@ -37,9 +42,16 @@ export default function () { return; } (async () => { - const projects = !!team - ? await getGitpodService().server.getTeamProjects(team.id) - : await getGitpodService().server.getUserProjects(); + let projects: Project[]; + if (!!team) { + projects = usePublicApiProjectsService + ? await listAllProjects({ teamId: team.id }) + : await getGitpodService().server.getTeamProjects(team.id); + } else { + projects = usePublicApiProjectsService + ? await listAllProjects({ userId: user?.id }) + : await getGitpodService().server.getUserProjects(); + } const project = projectSlug && projects.find((p) => (!!p.slug ? p.slug === projectSlug : p.name === projectSlug)); diff --git a/components/dashboard/src/projects/Prebuilds.tsx b/components/dashboard/src/projects/Prebuilds.tsx index 570e5145b5fb4b..adbe5fcdfc6ba5 100644 --- a/components/dashboard/src/projects/Prebuilds.tsx +++ b/components/dashboard/src/projects/Prebuilds.tsx @@ -22,11 +22,16 @@ import { TeamsContext, getCurrentTeam } from "../teams/teams-context"; import { shortCommitMessage } from "./render-utils"; import { Link } from "react-router-dom"; import { Disposable } from "vscode-jsonrpc"; +import { UserContext } from "../user-context"; +import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; +import { listAllProjects } from "../service/public-api"; export default function (props: { project?: Project; isAdminDashboard?: boolean }) { const location = useLocation(); const { teams } = useContext(TeamsContext); + const { user } = useContext(UserContext); + const { usePublicApiProjectsService } = useContext(FeatureFlagContext); const team = getCurrentTeam(location, teams); const match = useRouteMatch<{ team: string; resource: string }>("/(t/)?:team/:resource"); @@ -85,10 +90,16 @@ export default function (props: { project?: Project; isAdminDashboard?: boolean return; } (async () => { - const projects = !!team - ? await getGitpodService().server.getTeamProjects(team.id) - : await getGitpodService().server.getUserProjects(); - + let projects: Project[]; + if (!!team) { + projects = usePublicApiProjectsService + ? await listAllProjects({ teamId: team.id }) + : await getGitpodService().server.getTeamProjects(team.id); + } else { + projects = usePublicApiProjectsService + ? await listAllProjects({ userId: user?.id }) + : await getGitpodService().server.getUserProjects(); + } const newProject = projectSlug && projects.find((p) => (p.slug ? p.slug === projectSlug : p.name === projectSlug)); diff --git a/components/dashboard/src/projects/Project.tsx b/components/dashboard/src/projects/Project.tsx index 99e3bf7324dae0..4347b28cff0159 100644 --- a/components/dashboard/src/projects/Project.tsx +++ b/components/dashboard/src/projects/Project.tsx @@ -19,12 +19,17 @@ import NoAccess from "../icons/NoAccess.svg"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { openAuthorizeWindow } from "../provider-utils"; import Alert from "../components/Alert"; +import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; +import { listAllProjects } from "../service/public-api"; +import { UserContext } from "../user-context"; export default function () { const location = useLocation(); const history = useHistory(); const { teams } = useContext(TeamsContext); + const { user } = useContext(UserContext); + const { usePublicApiProjectsService } = useContext(FeatureFlagContext); const team = getCurrentTeam(location, teams); const match = useRouteMatch<{ team: string; resource: string }>("/(t/)?:team/:resource"); @@ -69,10 +74,16 @@ export default function () { if (!teams || !projectSlug) { return; } - const projects = !!team - ? await getGitpodService().server.getTeamProjects(team.id) - : await getGitpodService().server.getUserProjects(); - + let projects: Project[]; + if (!!team) { + projects = usePublicApiProjectsService + ? await listAllProjects({ teamId: team.id }) + : await getGitpodService().server.getTeamProjects(team.id); + } else { + projects = usePublicApiProjectsService + ? await listAllProjects({ userId: user?.id }) + : await getGitpodService().server.getUserProjects(); + } // Find project matching with slug, otherwise with name const project = projectSlug && projects.find((p) => (p.slug ? p.slug === projectSlug : p.name === projectSlug)); diff --git a/components/dashboard/src/projects/Projects.tsx b/components/dashboard/src/projects/Projects.tsx index 69f54346e2f118..97f72e170bf834 100644 --- a/components/dashboard/src/projects/Projects.tsx +++ b/components/dashboard/src/projects/Projects.tsx @@ -20,12 +20,17 @@ import ContextMenu from "../components/ContextMenu"; import ConfirmationModal from "../components/ConfirmationModal"; import { prebuildStatusIcon } from "./Prebuilds"; import Alert from "../components/Alert"; +import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; +import { listAllProjects } from "../service/public-api"; +import { UserContext } from "../user-context"; export default function () { const location = useLocation(); const history = useHistory(); const { teams } = useContext(TeamsContext); + const { user } = useContext(UserContext); + const { usePublicApiProjectsService } = useContext(FeatureFlagContext); const team = getCurrentTeam(location, teams); const [projects, setProjects] = useState([]); const [lastPrebuilds, setLastPrebuilds] = useState>(new Map()); @@ -42,9 +47,17 @@ export default function () { if (!teams) { return; } - const infos = !!team - ? await getGitpodService().server.getTeamProjects(team.id) - : await getGitpodService().server.getUserProjects(); + + let infos: Project[]; + if (!!team) { + infos = usePublicApiProjectsService + ? await listAllProjects({ teamId: team.id }) + : await getGitpodService().server.getTeamProjects(team.id); + } else { + infos = usePublicApiProjectsService + ? await listAllProjects({ userId: user?.id }) + : await getGitpodService().server.getUserProjects(); + } setProjects(infos); const map = new Map(); diff --git a/components/dashboard/src/service/public-api.ts b/components/dashboard/src/service/public-api.ts index ad355b67162e0b..00e2562d1dbcce 100644 --- a/components/dashboard/src/service/public-api.ts +++ b/components/dashboard/src/service/public-api.ts @@ -5,12 +5,14 @@ */ import { createConnectTransport, createPromiseClient } from "@bufbuild/connect-web"; -import { Team as ProtocolTeam } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol"; +import { Project as ProtocolProject, Team as ProtocolTeam } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol"; import { TeamsService } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_connectweb"; import { TokensService } from "@gitpod/public-api/lib/gitpod/experimental/v1/tokens_connectweb"; +import { ProjectsService } from "@gitpod/public-api/lib/gitpod/experimental/v1/projects_connectweb"; import { Team } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb"; import { TeamMemberInfo, TeamMemberRole } from "@gitpod/gitpod-protocol"; import { TeamMember, TeamRole } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb"; +import { Project } from "@gitpod/public-api/lib/gitpod/experimental/v1/projects_pb"; const transport = createConnectTransport({ baseUrl: `${window.location.protocol}//api.${window.location.host}`, @@ -19,6 +21,7 @@ const transport = createConnectTransport({ export const teamsService = createPromiseClient(TeamsService, transport); export const personalAccessTokensService = createPromiseClient(TokensService, transport); +export const projectsService = createPromiseClient(ProjectsService, transport); export function publicApiTeamToProtocol(team: Team): ProtocolTeam { return { @@ -58,3 +61,55 @@ export function publicApiTeamRoleToProtocol(role: TeamRole): TeamMemberRole { return "member"; } } + +export async function listAllProjects(opts: { userId?: string; teamId?: string }): Promise { + let pagination = { + page: 1, + pageSize: 100, + }; + + const response = await projectsService.listProjects({ + teamId: opts.teamId, + userId: opts.userId, + pagination, + }); + const results = response.projects; + + while (results.length < response.totalResults) { + pagination = { + pageSize: 100, + page: 1 + pagination.page, + }; + const response = await projectsService.listProjects({ + teamId: opts.teamId, + userId: opts.userId, + pagination, + }); + results.push(...response.projects); + } + + return results.map(projectToProtocol); +} + +export function projectToProtocol(project: Project): ProtocolProject { + return { + id: project.id, + name: project.name, + cloneUrl: project.cloneUrl, + creationTime: project.creationTime?.toDate().toISOString() || "", + slug: project.slug, + teamId: project.teamId, + userId: project.userId, + appInstallationId: "undefined", + settings: { + allowUsingPreviousPrebuilds: project.settings?.prebuild?.usePreviousPrebuilds, + keepOutdatedPrebuildsRunning: project.settings?.prebuild?.keepOutdatedPrebuildsRunning, + prebuildEveryNthCommit: project.settings?.prebuild?.prebuildEveryNth, + useIncrementalPrebuilds: project.settings?.prebuild?.enableIncrementalPrebuilds, + workspaceClasses: { + prebuild: project.settings?.workspace?.workspaceClass?.prebuild || "", + regular: project.settings?.workspace?.workspaceClass?.regular || "", + }, + }, + }; +} diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index ba2770fb96c1d1..03e31bc26cec47 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -1540,7 +1540,7 @@ func (gp *APIoverJSONRPC) CreateProject(ctx context.Context, options *CreateProj return } _params := []interface{}{options} - err = gp.C.Call(ctx, string(FunctionCreateProject), _params, nil) + err = gp.C.Call(ctx, string(FunctionCreateProject), _params, &res) return } @@ -1560,7 +1560,7 @@ func (gp *APIoverJSONRPC) GetUserProjects(ctx context.Context) (res []*Project, return } _params := []interface{}{} - err = gp.C.Call(ctx, string(FunctionGetUserProjects), _params, nil) + err = gp.C.Call(ctx, string(FunctionGetUserProjects), _params, &res) return } @@ -1570,7 +1570,7 @@ func (gp *APIoverJSONRPC) GetTeamProjects(ctx context.Context, teamID string) (r return } _params := []interface{}{teamID} - err = gp.C.Call(ctx, string(FunctionGetTeamProjects), _params, nil) + err = gp.C.Call(ctx, string(FunctionGetTeamProjects), _params, &res) return }