diff --git a/components/dashboard/src/components/AuthorizeGit.tsx b/components/dashboard/src/components/AuthorizeGit.tsx index cc3669e6401664..f1815cfadc081b 100644 --- a/components/dashboard/src/components/AuthorizeGit.tsx +++ b/components/dashboard/src/components/AuthorizeGit.tsx @@ -8,7 +8,6 @@ import { AuthProviderInfo } from "@gitpod/gitpod-protocol"; import { FC, useCallback, useContext } from "react"; import { Link } from "react-router-dom"; import { useAuthProviders } from "../data/auth-providers/auth-provider-query"; -import { useCurrentOrg } from "../data/organizations/orgs-query"; import { openAuthorizeWindow } from "../provider-utils"; import { getGitpodService } from "../service/service"; import { UserContext, useCurrentUser } from "../user-context"; @@ -16,6 +15,7 @@ import { Button } from "./Button"; import { Heading2, Heading3, Subheading } from "./typography/headings"; import classNames from "classnames"; import { iconForAuthProvider, simplifyProviderName } from "../provider-utils"; +import { useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; export function useNeedsGitAuthorization() { const authProviders = useAuthProviders(); @@ -28,7 +28,7 @@ export function useNeedsGitAuthorization() { export const AuthorizeGit: FC<{ className?: string }> = ({ className }) => { const { setUser } = useContext(UserContext); - const org = useCurrentOrg(); + const orgMembersInfo = useOrgMembersInfoQuery(); const authProviders = useAuthProviders(); const updateUser = useCallback(() => { getGitpodService().server.getLoggedInUser().then(setUser); @@ -62,7 +62,7 @@ export const AuthorizeGit: FC<{ className?: string }> = ({ className }) => { {verifiedProviders.length === 0 ? ( <> No Git integrations - {!!org.data?.isOwner ? ( + {!!orgMembersInfo.data?.isOwner ? (
You need to configure at least one Git integration. diff --git a/components/dashboard/src/components/UsageBasedBillingConfig.tsx b/components/dashboard/src/components/UsageBasedBillingConfig.tsx index 5e1efff651177f..7c578064243627 100644 --- a/components/dashboard/src/components/UsageBasedBillingConfig.tsx +++ b/components/dashboard/src/components/UsageBasedBillingConfig.tsx @@ -21,6 +21,7 @@ import { Button } from "./Button"; import { useCreateHoldPaymentIntentMutation } from "../data/billing/create-hold-payment-intent-mutation"; import { useToast } from "./toasts/Toasts"; import { ProgressBar } from "./ProgressBar"; +import { useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; const BASE_USAGE_LIMIT_FOR_STRIPE_USERS = 1000; @@ -33,6 +34,7 @@ let didAlreadyCallSubscribe = false; export default function UsageBasedBillingConfig({ hideSubheading = false }: Props) { const currentOrg = useCurrentOrg().data; + const orgMembersInfo = useOrgMembersInfoQuery().data; const attrId = currentOrg ? AttributionId.create(currentOrg) : undefined; const attributionId = attrId && AttributionId.render(attrId); const [showUpdateLimitModal, setShowUpdateLimitModal] = useState(false); @@ -154,8 +156,8 @@ export default function UsageBasedBillingConfig({ hideSubheading = false }: Prop // Pick a good initial value for the Stripe usage limit (base_limit * team_size) // FIXME: Should we ask the customer to confirm or edit this default limit? let limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS; - if (attrId?.kind === "team" && currentOrg) { - limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * currentOrg.members.length; + if (attrId?.kind === "team" && orgMembersInfo) { + limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * orgMembersInfo.members.length; } const newLimit = await getGitpodService().server.subscribeToStripe( attributionId, @@ -190,7 +192,7 @@ export default function UsageBasedBillingConfig({ hideSubheading = false }: Prop ); } }, - [attrId?.kind, attributionId, currentOrg, location.pathname, refreshSubscriptionDetails], + [attrId?.kind, attributionId, orgMembersInfo, location.pathname, refreshSubscriptionDetails], ); const showSpinner = !attributionId || isLoadingStripeSubscription || isCreatingSubscription; diff --git a/components/dashboard/src/data/organizations/org-invitation-query.ts b/components/dashboard/src/data/organizations/org-invitation-query.ts new file mode 100644 index 00000000000000..bcab41a9ea30b0 --- /dev/null +++ b/components/dashboard/src/data/organizations/org-invitation-query.ts @@ -0,0 +1,29 @@ +/** + * 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 { useCurrentOrg } from "./orgs-query"; +import { teamsService } from "../../service/public-api"; + +export const useOrgInvitationQuery = () => { + const org = useCurrentOrg().data; + + return useQuery<{ invitationId?: string }>({ + queryKey: getOrgInvitationQueryKey(org?.id ?? ""), + staleTime: 1000 * 60 * 10, // 10 minute + queryFn: async () => { + if (!org) { + throw new Error("No org selected."); + } + + const resp = await teamsService.getTeamInvitation({ teamId: org.id }); + return { invitationId: resp.teamInvitation?.id }; + }, + enabled: !!org, + }); +}; + +export const getOrgInvitationQueryKey = (orgId: string) => ["org-invitation", { orgId }]; diff --git a/components/dashboard/src/data/organizations/org-members-info-query.ts b/components/dashboard/src/data/organizations/org-members-info-query.ts new file mode 100644 index 00000000000000..ff4b4ef201ae7d --- /dev/null +++ b/components/dashboard/src/data/organizations/org-members-info-query.ts @@ -0,0 +1,42 @@ +/** + * 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 { OrgMemberInfo } from "@gitpod/gitpod-protocol"; +import { useQuery } from "@tanstack/react-query"; +import { useCurrentOrg } from "./orgs-query"; +import { publicApiTeamMembersToProtocol, teamsService } from "../../service/public-api"; +import { useCurrentUser } from "../../user-context"; +import { TeamRole } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb"; + +export interface OrgMembersInfo { + members: OrgMemberInfo[]; + isOwner: boolean; +} + +export const useOrgMembersInfoQuery = () => { + const user = useCurrentUser(); + const org = useCurrentOrg().data; + + return useQuery({ + queryKey: getOrgMembersInfoQueryKey(org?.id ?? "", user?.id ?? ""), + staleTime: 1000 * 60 * 5, // 5 minute + queryFn: async () => { + if (!org) { + throw new Error("No org selected."); + } + const resp = await teamsService.listTeamMembers({ teamId: org.id }); + return { + members: publicApiTeamMembersToProtocol(resp.members), + isOwner: + resp.members.findIndex((member) => member.userId === user?.id && member.role === TeamRole.OWNER) >= + 0, + }; + }, + enabled: !!org && !!user, + }); +}; + +export const getOrgMembersInfoQueryKey = (orgId: string, userId: string) => ["org-members", { orgId, userId }]; diff --git a/components/dashboard/src/data/organizations/orgs-query.ts b/components/dashboard/src/data/organizations/orgs-query.ts index a24c11186b2965..603d1e3c4b2b93 100644 --- a/components/dashboard/src/data/organizations/orgs-query.ts +++ b/components/dashboard/src/data/organizations/orgs-query.ts @@ -4,27 +4,30 @@ * See License.AGPL.txt in the project root for license information. */ -import { Organization, OrgMemberInfo, User } from "@gitpod/gitpod-protocol"; +import { Organization, User } from "@gitpod/gitpod-protocol"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; import { useLocation } from "react-router"; -import { publicApiTeamMembersToProtocol, publicApiTeamToProtocol, teamsService } from "../../service/public-api"; +import { teamsService } from "../../service/public-api"; import { useCurrentUser } from "../../user-context"; import { noPersistence } from "../setup"; +import { getOrgInvitationQueryKey } from "./org-invitation-query"; +import { getOrgMembersInfoQueryKey } from "./org-members-info-query"; -export interface OrganizationInfo extends Organization { - members: OrgMemberInfo[]; - isOwner: boolean; - invitationId?: string; -} +export type OrganizationInfo = Pick; export function useOrganizationsInvalidator() { const user = useCurrentUser(); + const org = useCurrentOrg(); const queryClient = useQueryClient(); return useCallback(() => { console.log("Invalidating orgs... " + JSON.stringify(getQueryKey(user))); queryClient.invalidateQueries(getQueryKey(user)); - }, [user, queryClient]); + if (org.data?.id && user?.id) { + queryClient.invalidateQueries(getOrgInvitationQueryKey(org.data.id)); + queryClient.invalidateQueries(getOrgMembersInfoQueryKey(org.data.id, user.id)); + } + }, [user, org.data, queryClient]); } export function useOrganizations() { @@ -39,18 +42,7 @@ export function useOrganizations() { } const response = await teamsService.listTeams({}); - const result: OrganizationInfo[] = []; - for (const org of response.teams) { - const members = publicApiTeamMembersToProtocol(org.members || []); - const isOwner = members.some((m) => m.role === "owner" && m.userId === user?.id); - result.push({ - ...publicApiTeamToProtocol(org), - members, - isOwner, - invitationId: org.teamInvitation?.id, - }); - } - return result; + return response.teams; }, { enabled: !!user, diff --git a/components/dashboard/src/menu/OrganizationSelector.tsx b/components/dashboard/src/menu/OrganizationSelector.tsx index b3930080177d56..59e0454522568a 100644 --- a/components/dashboard/src/menu/OrganizationSelector.tsx +++ b/components/dashboard/src/menu/OrganizationSelector.tsx @@ -12,11 +12,13 @@ import { useCurrentOrg, useOrganizations } from "../data/organizations/orgs-quer import { useLocation } from "react-router"; import { User } from "@gitpod/gitpod-protocol"; import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query"; +import { useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; export default function OrganizationSelector() { const user = useCurrentUser(); const orgs = useOrganizations(); const currentOrg = useCurrentOrg(); + const orgMembersInfo = useOrgMembersInfoQuery().data; const { data: billingMode } = useOrgBillingMode(); const getOrgURL = useGetOrgURL(); @@ -39,9 +41,9 @@ export default function OrganizationSelector() { ), diff --git a/components/dashboard/src/teams/Members.tsx b/components/dashboard/src/teams/Members.tsx index d83c2c2b4d8b56..5a14ad90ba8aa1 100644 --- a/components/dashboard/src/teams/Members.tsx +++ b/components/dashboard/src/teams/Members.tsx @@ -22,10 +22,14 @@ import { SpinnerLoader } from "../components/Loader"; import { Delayed } from "../components/Delayed"; import { InputField } from "../components/forms/InputField"; import { InputWithCopy } from "../components/InputWithCopy"; +import { useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; +import { useOrgInvitationQuery } from "../data/organizations/org-invitation-query"; export default function MembersPage() { const user = useCurrentUser(); const org = useCurrentOrg(); + const orgInvitation = useOrgInvitationQuery(); + const orgMembersInfo = useOrgMembersInfoQuery(); const invalidateOrgs = useOrganizationsInvalidator(); const [showInviteModal, setShowInviteModal] = useState(false); @@ -38,14 +42,14 @@ export default function MembersPage() { } // orgs without an invitation id invite members through their own login page const link = new URL(window.location.href); - if (!org.data.invitationId) { + if (!orgInvitation.data?.invitationId) { link.pathname = "/login/" + org.data.slug; } else { link.pathname = "/orgs/join"; - link.search = "?inviteId=" + org.data.invitationId; + link.search = "?inviteId=" + orgInvitation.data.invitationId; } return link.href; - }, [org.data]); + }, [org.data, orgInvitation.data]); const resetInviteLink = async () => { await teamsService.resetTeamInvitation({ teamId: org.data?.id }); @@ -66,14 +70,14 @@ export default function MembersPage() { }; const isRemainingOwner = useMemo(() => { - const owners = org.data?.members.filter((m) => m.role === "owner"); + const owners = orgMembersInfo.data?.members.filter((m) => m.role === "owner"); return owners?.length === 1 && owners[0].userId === user?.id; - }, [org.data?.members, user?.id]); + }, [orgMembersInfo.data?.members, user?.id]); const isOwner = useMemo(() => { - const owners = org.data?.members.filter((m) => m.role === "owner"); + const owners = orgMembersInfo.data?.members.filter((m) => m.role === "owner"); return !!owners?.some((o) => o.userId === user?.id); - }, [org.data?.members, user?.id]); + }, [orgMembersInfo.data?.members, user?.id]); // Note: We would hardly get here, but just in case. We should show a loader instead of blank section. if (org.isLoading) { @@ -85,7 +89,7 @@ export default function MembersPage() { } const filteredMembers = - org.data?.members.filter((m) => { + orgMembersInfo.data?.members.filter((m) => { if (!!roleFilter && m.role !== roleFilter) { return false; } @@ -204,7 +208,7 @@ export default function MembersPage() { - {org.data?.isOwner ? ( + {orgMembersInfo.data?.isOwner ? ( - {!!org?.data?.invitationId && ( + {!!orgInvitation?.data?.invitationId && ( diff --git a/components/dashboard/src/teams/OrgSettingsPage.tsx b/components/dashboard/src/teams/OrgSettingsPage.tsx index 6ac480cc412ba9..24ff1804aa348d 100644 --- a/components/dashboard/src/teams/OrgSettingsPage.tsx +++ b/components/dashboard/src/teams/OrgSettingsPage.tsx @@ -4,7 +4,6 @@ * See License.AGPL.txt in the project root for license information. */ -import { Team } from "@gitpod/gitpod-protocol"; import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode"; import { useMemo } from "react"; import { Redirect } from "react-router"; @@ -12,15 +11,15 @@ import Header from "../components/Header"; import { SpinnerLoader } from "../components/Loader"; import { PageWithSubMenu } from "../components/PageWithSubMenu"; import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query"; -import { useCurrentOrg } from "../data/organizations/orgs-query"; import { useFeatureFlag } from "../data/featureflag-query"; +import { useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; export interface OrgSettingsPageProps { children: React.ReactNode; } export function OrgSettingsPage({ children }: OrgSettingsPageProps) { - const org = useCurrentOrg(); + const orgMembersInfo = useOrgMembersInfoQuery(); const orgBillingMode = useOrgBillingMode(); const oidcServiceEnabled = useFeatureFlag("oidcServiceEnabled"); const orgGitAuthProviders = useFeatureFlag("orgGitAuthProviders"); @@ -28,20 +27,19 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) { const menu = useMemo( () => getTeamSettingsMenu({ - team: org.data, billingMode: orgBillingMode.data, ssoEnabled: oidcServiceEnabled, orgGitAuthProviders, - isOwner: org.data?.isOwner, + isOwner: orgMembersInfo.data?.isOwner, }), - [org.data, orgBillingMode.data, oidcServiceEnabled, orgGitAuthProviders], + [orgMembersInfo.data, orgBillingMode.data, oidcServiceEnabled, orgGitAuthProviders], ); const title = "Organization Settings"; const subtitle = "Manage your organization's settings."; // Render as much of the page as we can in a loading state to avoid content shift - if (org.isLoading || orgBillingMode.isLoading) { + if (orgMembersInfo.isLoading || orgBillingMode.isLoading) { return (
@@ -56,7 +54,7 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) { const onlyForOwner = false; // After we've loaded, ensure user is an owner, if not, redirect - if (onlyForOwner && !org.data?.isOwner) { + if (onlyForOwner && !orgMembersInfo.data?.isOwner) { return ; } @@ -68,7 +66,6 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) { } function getTeamSettingsMenu(params: { - team?: Team; billingMode?: BillingMode; ssoEnabled?: boolean; orgGitAuthProviders: boolean; diff --git a/components/dashboard/src/teams/TeamSettings.tsx b/components/dashboard/src/teams/TeamSettings.tsx index f67711a39f916e..cdd2ab578b852a 100644 --- a/components/dashboard/src/teams/TeamSettings.tsx +++ b/components/dashboard/src/teams/TeamSettings.tsx @@ -26,10 +26,12 @@ import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal" import { InputField } from "../components/forms/InputField"; import { InputWithCopy } from "../components/InputWithCopy"; import { ReactComponent as Stack } from "../icons/Stack.svg"; +import { OrgMembersInfo, useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; export default function TeamSettingsPage() { const user = useCurrentUser(); const org = useCurrentOrg().data; + const orgMembersInfo = useOrgMembersInfoQuery().data; const invalidateOrgs = useOrganizationsInvalidator(); const [modal, setModal] = useState(false); const [teamNameToDelete, setTeamNameToDelete] = useState(""); @@ -50,7 +52,7 @@ export default function TeamSettingsPage() { const updateTeamInformation = useCallback( async (e: React.FormEvent) => { - if (!org?.isOwner) { + if (!orgMembersInfo?.isOwner) { return; } e.preventDefault(); @@ -67,7 +69,7 @@ export default function TeamSettingsPage() { console.error(error); } }, - [orgFormIsValid, updateOrg, teamName, org], + [orgFormIsValid, updateOrg, teamName, orgMembersInfo?.isOwner], ); const deleteTeam = useCallback(async () => { @@ -104,11 +106,11 @@ export default function TeamSettingsPage() { value={teamName} error={teamNameError.message} onChange={setTeamName} - disabled={!org?.isOwner} + disabled={!orgMembersInfo?.isOwner} onBlur={teamNameError.onBlur} /> - {org?.isOwner && ( + {orgMembersInfo?.isOwner && ( @@ -117,7 +119,7 @@ export default function TeamSettingsPage() { - {user?.organizationId !== org?.id && org?.isOwner && ( + {user?.organizationId !== org?.id && orgMembersInfo?.isOwner && ( <> Delete Organization @@ -170,8 +172,8 @@ export default function TeamSettingsPage() { ); } -function OrgSettingsForm(props: { org?: OrganizationInfo }) { - const { org } = props; +function OrgSettingsForm(props: { org?: OrganizationInfo; orgMembersInfo?: OrgMembersInfo }) { + const { org, orgMembersInfo } = props; const { data: settings, isLoading } = useOrgSettingsQuery(); const { data: imageInfo } = useDefaultWorkspaceImageQuery(); const updateTeamSettings = useUpdateOrgSettingsMutation(); @@ -183,7 +185,7 @@ function OrgSettingsForm(props: { org?: OrganizationInfo }) { if (!org?.id) { throw new Error("no organization selected"); } - if (!org.isOwner) { + if (!orgMembersInfo?.isOwner) { throw new Error("no organization settings change permission"); } try { @@ -195,7 +197,7 @@ function OrgSettingsForm(props: { org?: OrganizationInfo }) { console.error(error); } }, - [updateTeamSettings, org?.id, org?.isOwner, settings], + [updateTeamSettings, org?.id, orgMembersInfo?.isOwner, settings], ); return ( @@ -225,7 +227,7 @@ function OrgSettingsForm(props: { org?: OrganizationInfo }) { hint="Allow workspaces created within an Organization to share the workspace with any authenticated user." checked={!settings?.workspaceSharingDisabled} onChange={(checked) => handleUpdateTeamSettings({ workspaceSharingDisabled: !checked })} - disabled={isLoading || !org?.isOwner} + disabled={isLoading || !orgMembersInfo?.isOwner} /> Workspace Images @@ -234,7 +236,7 @@ function OrgSettingsForm(props: { org?: OrganizationInfo }) { setShowImageEditModal(true)} diff --git a/components/dashboard/src/teams/git-integrations/GitIntegrationListItem.tsx b/components/dashboard/src/teams/git-integrations/GitIntegrationListItem.tsx index 352f536d5d19c9..5008a1bd596d36 100644 --- a/components/dashboard/src/teams/git-integrations/GitIntegrationListItem.tsx +++ b/components/dashboard/src/teams/git-integrations/GitIntegrationListItem.tsx @@ -11,9 +11,9 @@ import { ContextMenuEntry } from "../../components/ContextMenu"; import { Item, ItemField, ItemFieldContextMenu, ItemFieldIcon } from "../../components/ItemsList"; import { useDeleteOrgAuthProviderMutation } from "../../data/auth-providers/delete-org-auth-provider-mutation"; import { GitIntegrationModal } from "./GitIntegrationModal"; -import { useCurrentOrg } from "../../data/organizations/orgs-query"; import { ModalFooterAlert } from "../../components/Modal"; import { useToast } from "../../components/toasts/Toasts"; +import { useOrgMembersInfoQuery } from "../../data/organizations/org-members-info-query"; type Props = { provider: AuthProviderEntry; @@ -22,10 +22,10 @@ export const GitIntegrationListItem: FunctionComponent = ({ provider }) = const [showEditModal, setShowEditModal] = useState(false); const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); const deleteAuthProvider = useDeleteOrgAuthProviderMutation(); - const { data: org } = useCurrentOrg(); + const orgMembersInfo = useOrgMembersInfoQuery().data; const { toast } = useToast(); - const memberCount = org?.members.length ?? 1; + const memberCount = orgMembersInfo?.members.length ?? 1; const menuEntries = useMemo(() => { const result: ContextMenuEntry[] = []; diff --git a/components/dashboard/src/usage/UsageSummary.tsx b/components/dashboard/src/usage/UsageSummary.tsx index cea828a7bff793..f2d390d46bbe23 100644 --- a/components/dashboard/src/usage/UsageSummary.tsx +++ b/components/dashboard/src/usage/UsageSummary.tsx @@ -5,16 +5,16 @@ */ import { FC } from "react"; -import { useCurrentOrg } from "../data/organizations/orgs-query"; import { Subheading } from "../components/typography/headings"; import { Link } from "react-router-dom"; import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query"; +import { useOrgMembersInfoQuery } from "../data/organizations/org-members-info-query"; type Props = { creditsUsed?: number; }; export const UsageSummaryData: FC = ({ creditsUsed }) => { - const currentOrg = useCurrentOrg(); + const orgMembersInfo = useOrgMembersInfoQuery().data; const { data: billingMode } = useOrgBillingMode(); return ( @@ -26,7 +26,7 @@ export const UsageSummaryData: FC = ({ creditsUsed }) => { {creditsUsed !== undefined ? creditsUsed.toLocaleString() : "-"}
- {currentOrg.data && currentOrg.data.isOwner && billingMode?.mode === "usage-based" && ( + {orgMembersInfo && orgMembersInfo.isOwner && billingMode?.mode === "usage-based" && (
diff --git a/components/gitpod-protocol/src/attribution.ts b/components/gitpod-protocol/src/attribution.ts index 71ded9b6fe4557..094f3d13babfca 100644 --- a/components/gitpod-protocol/src/attribution.ts +++ b/components/gitpod-protocol/src/attribution.ts @@ -21,7 +21,7 @@ export namespace AttributionId { return { kind: "team", teamId: organizationId }; } - export function create(organization: Organization): AttributionId { + export function create(organization: Pick): AttributionId { return createFromOrganizationId(organization.id); } diff --git a/components/server/src/api/teams.spec.db.ts b/components/server/src/api/teams.spec.db.ts index 710317697e0e66..87cf71988dafcc 100644 --- a/components/server/src/api/teams.spec.db.ts +++ b/components/server/src/api/teams.spec.db.ts @@ -5,11 +5,10 @@ */ import { Code, ConnectError, PromiseClient, createPromiseClient } from "@connectrpc/connect"; import { createConnectTransport } from "@connectrpc/connect-node"; -import { Timestamp } from "@bufbuild/protobuf"; import { TeamDB, TypeORM, UserDB, testContainer } from "@gitpod/gitpod-db/lib"; import { DBTeam } from "@gitpod/gitpod-db/lib/typeorm/entity/db-team"; import { TeamsService as TeamsServiceDefinition } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_connect"; -import { GetTeamRequest, Team, TeamMember, TeamRole } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb"; +import { GetTeamRequest, Team } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb"; import { suite, test, timeout } from "@testdeck/mocha"; import * as chai from "chai"; import * as http from "http"; @@ -109,7 +108,6 @@ export class APITeamsServiceSpec { const userDB = this.container.get(UserDB); const user = await userDB.storeUser(await userDB.newUser()); const team = await teamDB.createTeam(user.id, "myteam"); - const invite = await teamDB.resetGenericInvite(team.id); const response = await this.client.getTeam( new GetTeamRequest({ @@ -121,18 +119,6 @@ export class APITeamsServiceSpec { id: team.id, slug: team.slug, name: team.name, - members: [ - new TeamMember({ - userId: user.id, - avatarUrl: user.avatarUrl, - fullName: user.fullName, - role: TeamRole.OWNER, - memberSince: Timestamp.fromDate(new Date(team.creationTime)), - }), - ], - teamInvitation: { - id: invite.id, - }, }), ); } diff --git a/components/server/src/api/teams.ts b/components/server/src/api/teams.ts index c36ecc798b4c57..2e4948450c06da 100644 --- a/components/server/src/api/teams.ts +++ b/components/server/src/api/teams.ts @@ -14,16 +14,19 @@ import { DeleteTeamMemberResponse, DeleteTeamRequest, DeleteTeamResponse, + GetTeamInvitationRequest, + GetTeamInvitationResponse, GetTeamRequest, GetTeamResponse, JoinTeamRequest, JoinTeamResponse, + ListTeamMembersRequest, + ListTeamMembersResponse, ListTeamsRequest, ListTeamsResponse, ResetTeamInvitationRequest, ResetTeamInvitationResponse, Team, - TeamInvitation, TeamMember, TeamRole, UpdateTeamMemberRequest, @@ -33,6 +36,7 @@ import { TeamDB } from "@gitpod/gitpod-db/lib"; import { validate } from "uuid"; import { OrgMemberInfo, Organization, TeamMembershipInvite } from "@gitpod/gitpod-protocol"; import { Timestamp } from "@bufbuild/protobuf"; +import { UnaryImpl } from "@connectrpc/connect/dist/cjs/implementation"; @injectable() export class APITeamsService implements ServiceImpl { @@ -81,6 +85,13 @@ export class APITeamsService implements ServiceImpl public async deleteTeamMember(req: DeleteTeamMemberRequest): Promise { throw new ConnectError("unimplemented", Code.Unimplemented); } + public async getTeamInvitation(req: GetTeamInvitationRequest): Promise { + throw new ConnectError("unimplemented", Code.Unimplemented); + } + + public async listTeamMembers(req: ListTeamMembersRequest): Promise { + throw new ConnectError("unimplemented", Code.Unimplemented); + } } export function toAPITeam(team: Organization, members: OrgMemberInfo[], invite: TeamMembershipInvite): Team { @@ -88,10 +99,6 @@ export function toAPITeam(team: Organization, members: OrgMemberInfo[], invite: id: team.id, name: team.name, slug: team.slug, - teamInvitation: new TeamInvitation({ - id: invite.id, - }), - members: members.map(memberToAPI), }); }