Skip to content

Commit f6bf401

Browse files
authored
[dashboard] use organization v2 shapes (#18970)
1 parent 0fba511 commit f6bf401

Some content is hidden

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

53 files changed

+2457
-2021
lines changed

components/dashboard/src/components/AuthorizeGit.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import { AuthProviderInfo } from "@gitpod/gitpod-protocol";
88
import { FC, useCallback, useContext } from "react";
99
import { Link } from "react-router-dom";
1010
import { useAuthProviders } from "../data/auth-providers/auth-provider-query";
11-
import { useCurrentOrg } from "../data/organizations/orgs-query";
1211
import { openAuthorizeWindow } from "../provider-utils";
1312
import { getGitpodService } from "../service/service";
1413
import { UserContext, useCurrentUser } from "../user-context";
1514
import { Button } from "./Button";
1615
import { Heading2, Heading3, Subheading } from "./typography/headings";
1716
import classNames from "classnames";
1817
import { iconForAuthProvider, simplifyProviderName } from "../provider-utils";
18+
import { useIsOwner } from "../data/organizations/members-query";
1919

2020
export function useNeedsGitAuthorization() {
2121
const authProviders = useAuthProviders();
@@ -28,7 +28,7 @@ export function useNeedsGitAuthorization() {
2828

2929
export const AuthorizeGit: FC<{ className?: string }> = ({ className }) => {
3030
const { setUser } = useContext(UserContext);
31-
const org = useCurrentOrg();
31+
const owner = useIsOwner();
3232
const authProviders = useAuthProviders();
3333
const updateUser = useCallback(() => {
3434
getGitpodService().server.getLoggedInUser().then(setUser);
@@ -57,7 +57,7 @@ export const AuthorizeGit: FC<{ className?: string }> = ({ className }) => {
5757
{verifiedProviders.length === 0 ? (
5858
<>
5959
<Heading3 className="pb-2">No Git integrations</Heading3>
60-
{!!org.data?.isOwner ? (
60+
{!!owner ? (
6161
<div className="px-6">
6262
<Subheading>You need to configure at least one Git integration.</Subheading>
6363
<Link to="/settings/git">

components/dashboard/src/components/PendingChangesDropdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import ContextMenu, { ContextMenuEntry } from "./ContextMenu";
88
import CaretDown from "../icons/CaretDown.svg";
9-
import { WorkspaceGitStatus } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_pb";
9+
import { WorkspaceGitStatus } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
1010

1111
export default function PendingChangesDropdown({ gitStatus }: { gitStatus?: WorkspaceGitStatus }) {
1212
const headingStyle = "text-gray-500 dark:text-gray-400 text-left";

components/dashboard/src/components/PrebuildLogs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { getGitpodService } from "../service/service";
1717
import { PrebuildStatus } from "../projects/Prebuilds";
1818
import { converter, workspaceClient } from "../service/public-api";
19-
import { GetWorkspaceRequest, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_pb";
19+
import { GetWorkspaceRequest, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
2020

2121
const WorkspaceLogs = React.lazy(() => import("./WorkspaceLogs"));
2222

components/dashboard/src/components/UsageBasedBillingConfig.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Button } from "./Button";
2121
import { useCreateHoldPaymentIntentMutation } from "../data/billing/create-hold-payment-intent-mutation";
2222
import { useToast } from "./toasts/Toasts";
2323
import { ProgressBar } from "./ProgressBar";
24+
import { useListOrganizationMembers } from "../data/organizations/members-query";
2425

2526
const BASE_USAGE_LIMIT_FOR_STRIPE_USERS = 1000;
2627

@@ -33,8 +34,9 @@ let didAlreadyCallSubscribe = false;
3334

3435
export default function UsageBasedBillingConfig({ hideSubheading = false }: Props) {
3536
const currentOrg = useCurrentOrg().data;
36-
const attrId = currentOrg ? AttributionId.create(currentOrg) : undefined;
37+
const attrId = currentOrg ? AttributionId.createFromOrganizationId(currentOrg.id) : undefined;
3738
const attributionId = attrId && AttributionId.render(attrId);
39+
const members = useListOrganizationMembers().data;
3840
const [showUpdateLimitModal, setShowUpdateLimitModal] = useState<boolean>(false);
3941
const [stripeSubscriptionId, setStripeSubscriptionId] = useState<string | undefined>();
4042
const [isLoadingStripeSubscription, setIsLoadingStripeSubscription] = useState<boolean>(true);
@@ -155,7 +157,7 @@ export default function UsageBasedBillingConfig({ hideSubheading = false }: Prop
155157
// FIXME: Should we ask the customer to confirm or edit this default limit?
156158
let limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS;
157159
if (attrId?.kind === "team" && currentOrg) {
158-
limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * currentOrg.members.length;
160+
limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * (members?.length || 0);
159161
}
160162
const newLimit = await getGitpodService().server.subscribeToStripe(
161163
attributionId,
@@ -190,7 +192,7 @@ export default function UsageBasedBillingConfig({ hideSubheading = false }: Prop
190192
);
191193
}
192194
},
193-
[attrId?.kind, attributionId, currentOrg, location.pathname, refreshSubscriptionDetails],
195+
[members, attrId?.kind, attributionId, currentOrg, location.pathname, refreshSubscriptionDetails],
194196
);
195197

196198
const showSpinner = !attributionId || isLoadingStripeSubscription || isCreatingSubscription;

components/dashboard/src/data/organizations/create-org-mutation.ts

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

7-
import { Organization } from "@gitpod/gitpod-protocol";
87
import { useMutation } from "@tanstack/react-query";
98
import { useOrganizationsInvalidator } from "./orgs-query";
10-
import { publicApiTeamToProtocol, teamsService } from "../../service/public-api";
9+
import { organizationClient } from "../../service/public-api";
10+
import { Organization } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
1111

1212
type CreateOrgArgs = Pick<Organization, "name">;
1313

@@ -16,13 +16,12 @@ export const useCreateOrgMutation = () => {
1616

1717
return useMutation<Organization, Error, CreateOrgArgs>({
1818
mutationFn: async ({ name }) => {
19-
const { team } = await teamsService.createTeam({ name });
20-
if (!team) {
21-
throw new Error("Error creating team");
19+
const { organization } = await organizationClient.createOrganization({ name });
20+
if (!organization) {
21+
throw new Error("Error creating organization");
2222
}
2323

24-
const org = publicApiTeamToProtocol(team);
25-
return org;
24+
return organization;
2625
},
2726
onSuccess(newOrg) {
2827
invalidateOrgs();
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8+
import { useCallback } from "react";
9+
import { organizationClient } from "../../service/public-api";
10+
import { useCurrentOrg } from "./orgs-query";
11+
12+
export function useInviteInvalidator() {
13+
const organizationId = useCurrentOrg().data?.id;
14+
const queryClient = useQueryClient();
15+
return useCallback(() => {
16+
queryClient.invalidateQueries(getQueryKey(organizationId));
17+
}, [organizationId, queryClient]);
18+
}
19+
20+
export function useInvitationId() {
21+
const organizationId = useCurrentOrg().data?.id;
22+
const query = useQuery<string, Error>(
23+
getQueryKey(organizationId),
24+
async () => {
25+
const response = await organizationClient.getOrganizationInvitation({
26+
organizationId,
27+
});
28+
return response.invitationId;
29+
},
30+
{
31+
enabled: !!organizationId,
32+
},
33+
);
34+
return query;
35+
}
36+
37+
export function useResetInvitationId() {
38+
const invalidate = useInviteInvalidator();
39+
return useMutation<void, Error, string>({
40+
mutationFn: async (orgId) => {
41+
if (!orgId) {
42+
throw new Error("No current organization selected");
43+
}
44+
45+
await organizationClient.resetOrganizationInvitation({
46+
organizationId: orgId,
47+
});
48+
//TODO update useInvitation Query
49+
},
50+
onSuccess(updatedOrg) {
51+
invalidate();
52+
},
53+
});
54+
}
55+
56+
function getQueryKey(organizationId: string | undefined) {
57+
return ["invitationId", organizationId || "undefined"];
58+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { OrganizationMember, OrganizationRole } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
8+
import { useQuery, useQueryClient } from "@tanstack/react-query";
9+
import { useCallback, useMemo } from "react";
10+
import { organizationClient } from "../../service/public-api";
11+
import { useCurrentUser } from "../../user-context";
12+
import { useCurrentOrg } from "./orgs-query";
13+
14+
export function useOrganizationMembersInvalidator() {
15+
const organizationId = useCurrentOrg().data?.id;
16+
const queryClient = useQueryClient();
17+
return useCallback(() => {
18+
queryClient.invalidateQueries(getQueryKey(organizationId));
19+
}, [organizationId, queryClient]);
20+
}
21+
22+
export function useListOrganizationMembers() {
23+
const organizationId = useCurrentOrg().data?.id;
24+
const query = useQuery<OrganizationMember[], Error>(
25+
getQueryKey(organizationId),
26+
async () => {
27+
const response = await organizationClient.listOrganizationMembers({
28+
organizationId,
29+
pagination: {
30+
pageSize: 1000,
31+
},
32+
});
33+
return response.members;
34+
},
35+
{
36+
staleTime: 1000 * 60 * 5, // 5 minutes
37+
enabled: !!organizationId,
38+
},
39+
);
40+
return query;
41+
}
42+
43+
export function useIsOwner(): boolean {
44+
const user = useCurrentUser();
45+
const members = useListOrganizationMembers();
46+
const isOwner = useMemo(() => {
47+
return members?.data?.some((m) => m.userId === user?.id && m.role === OrganizationRole.OWNER);
48+
}, [members?.data, user?.id]);
49+
return !!isOwner;
50+
}
51+
52+
function getQueryKey(organizationId: string | undefined) {
53+
return ["listOrganizationMembers", organizationId || "undefined"];
54+
}

components/dashboard/src/data/organizations/org-settings-query.ts

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

7-
import { OrganizationSettings } from "@gitpod/gitpod-protocol";
8-
import { useQuery } from "@tanstack/react-query";
9-
import { getGitpodService } from "../../service/service";
7+
import { useQuery, useQueryClient } from "@tanstack/react-query";
8+
import { organizationClient } from "../../service/public-api";
9+
import { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
10+
import { useCallback } from "react";
1011
import { useCurrentOrg } from "./orgs-query";
1112

12-
export type OrgSettingsResult = OrganizationSettings;
13+
export function useOrgSettingsQueryInvalidator() {
14+
const organizationId = useCurrentOrg().data?.id;
15+
const queryClient = useQueryClient();
16+
return useCallback(() => {
17+
queryClient.invalidateQueries(getQueryKey(organizationId));
18+
}, [organizationId, queryClient]);
19+
}
1320

14-
export const useOrgSettingsQuery = () => {
15-
const org = useCurrentOrg().data;
16-
17-
return useQuery<OrgSettingsResult>({
18-
queryKey: getOrgSettingsQueryKey(org?.id ?? ""),
19-
staleTime: 1000 * 60 * 1, // 1 minute
20-
queryFn: async () => {
21-
if (!org) {
21+
export function useOrgSettingsQuery() {
22+
const organizationId = useCurrentOrg().data?.id;
23+
return useQuery<OrganizationSettings, Error>(
24+
getQueryKey(organizationId),
25+
async () => {
26+
if (!organizationId) {
2227
throw new Error("No org selected.");
2328
}
2429

25-
const settings = await getGitpodService().server.getOrgSettings(org.id);
26-
return settings || null;
30+
const settings = await organizationClient.getOrganizationSettings({ organizationId });
31+
return settings.settings || new OrganizationSettings();
32+
},
33+
{
34+
enabled: !!organizationId,
2735
},
28-
enabled: !!org,
29-
});
30-
};
36+
);
37+
}
3138

32-
export const getOrgSettingsQueryKey = (teamId: string) => ["org-settings", { teamId }];
39+
function getQueryKey(organizationId?: string) {
40+
return ["getOrganizationSettings", organizationId || "undefined"];
41+
}

components/dashboard/src/data/organizations/orgs-query.ts

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

7-
import { Organization, OrgMemberInfo, User } from "@gitpod/gitpod-protocol";
7+
import { User } from "@gitpod/gitpod-protocol";
88
import { useQuery, useQueryClient } from "@tanstack/react-query";
99
import { useCallback } from "react";
1010
import { useLocation } from "react-router";
11-
import { publicApiTeamMembersToProtocol, publicApiTeamToProtocol, teamsService } from "../../service/public-api";
11+
import { organizationClient } from "../../service/public-api";
1212
import { useCurrentUser } from "../../user-context";
1313
import { noPersistence } from "../setup";
14-
15-
export interface OrganizationInfo extends Organization {
16-
members: OrgMemberInfo[];
17-
isOwner: boolean;
18-
invitationId?: string;
19-
}
14+
import { Organization } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
2015

2116
export function useOrganizationsInvalidator() {
2217
const user = useCurrentUser();
@@ -29,7 +24,7 @@ export function useOrganizationsInvalidator() {
2924

3025
export function useOrganizations() {
3126
const user = useCurrentUser();
32-
const query = useQuery<OrganizationInfo[], Error>(
27+
const query = useQuery<Organization[], Error>(
3328
getQueryKey(user),
3429
async () => {
3530
console.log("Fetching orgs... " + JSON.stringify(getQueryKey(user)));
@@ -38,19 +33,8 @@ export function useOrganizations() {
3833
return [];
3934
}
4035

41-
const response = await teamsService.listTeams({});
42-
const result: OrganizationInfo[] = [];
43-
for (const org of response.teams) {
44-
const members = publicApiTeamMembersToProtocol(org.members || []);
45-
const isOwner = members.some((m) => m.role === "owner" && m.userId === user?.id);
46-
result.push({
47-
...publicApiTeamToProtocol(org),
48-
members,
49-
isOwner,
50-
invitationId: org.teamInvitation?.id,
51-
});
52-
}
53-
return result;
36+
const response = await organizationClient.listOrganizations({});
37+
return response.organizations;
5438
},
5539
{
5640
enabled: !!user,
@@ -68,7 +52,7 @@ function getQueryKey(user?: User) {
6852
}
6953

7054
// Custom hook to return the current org if one is selected
71-
export function useCurrentOrg(): { data?: OrganizationInfo; isLoading: boolean } {
55+
export function useCurrentOrg(): { data?: Organization; isLoading: boolean } {
7256
const location = useLocation();
7357
const orgs = useOrganizations();
7458
const user = useCurrentUser();

0 commit comments

Comments
 (0)