Skip to content

Commit f601310

Browse files
committed
Merge remote-tracking branch 'origin/main' into ft/org-welcome-msg
Tool: gitpod/catfood.gitpod.cloud
2 parents 0bad6df + cdd79f3 commit f601310

16 files changed

+106
-57
lines changed

components/dashboard/src/Insights.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import { useTemporaryState } from "./hooks/use-temporary-value";
2626
import { DownloadIcon } from "lucide-react";
2727
import { Button } from "@podkit/buttons/Button";
2828
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@podkit/dropdown/DropDown";
29-
import { useInstallationConfiguration } from "./data/installation/default-workspace-image-query";
29+
import { useInstallationConfiguration } from "./data/installation/installation-config-query";
30+
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
3031

3132
export const Insights = () => {
3233
const toDate = useMemo(() => Timestamp.fromDate(new Date()), []);
@@ -49,6 +50,9 @@ export const Insights = () => {
4950
const grouped = Object.groupBy(sessions, (ws) => ws.workspace?.id ?? "unknown");
5051
const [page, setPage] = useState(0);
5152

53+
const isLackingPermissions =
54+
errorMessage instanceof ApplicationError && errorMessage.code === ErrorCodes.PERMISSION_DENIED;
55+
5256
return (
5357
<>
5458
<Header title="Insights" subtitle="Insights into workspace sessions in your organization" />
@@ -59,7 +63,7 @@ export const Insights = () => {
5963
"md:flex-row md:items-center md:space-x-4 md:space-y-0",
6064
)}
6165
>
62-
<DownloadUsage to={toDate} />
66+
<DownloadUsage to={toDate} disabled={isLackingPermissions} />
6367
</div>
6468

6569
<div
@@ -71,7 +75,16 @@ export const Insights = () => {
7175

7276
{errorMessage && (
7377
<Alert type="error" className="mt-4">
74-
{errorMessage instanceof Error ? errorMessage.message : "An error occurred."}
78+
{isLackingPermissions ? (
79+
<>
80+
You don't have <span className="font-medium">Owner</span> permissions to access this
81+
organization's insights.
82+
</>
83+
) : errorMessage instanceof Error ? (
84+
errorMessage.message
85+
) : (
86+
"An error occurred."
87+
)}
7588
</Alert>
7689
)}
7790

@@ -151,8 +164,9 @@ export const Insights = () => {
151164

152165
type DownloadUsageProps = {
153166
to: Timestamp;
167+
disabled?: boolean;
154168
};
155-
export const DownloadUsage = ({ to }: DownloadUsageProps) => {
169+
export const DownloadUsage = ({ to, disabled }: DownloadUsageProps) => {
156170
const { data: org } = useCurrentOrg();
157171
const { toast } = useToast();
158172
// When we start the download, we disable the button for a short time
@@ -184,7 +198,7 @@ export const DownloadUsage = ({ to }: DownloadUsageProps) => {
184198
return (
185199
<DropdownMenu>
186200
<DropdownMenuTrigger asChild>
187-
<Button variant="secondary" className="gap-1" disabled={downloadDisabled}>
201+
<Button variant="secondary" className="gap-1" disabled={disabled || downloadDisabled}>
188202
<DownloadIcon strokeWidth={3} className="w-4" />
189203
<span>Export as CSV</span>
190204
</Button>

components/dashboard/src/data/installation/default-workspace-image-query.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,3 @@ export const useInstallationDefaultWorkspaceImageQuery = () => {
1717
},
1818
});
1919
};
20-
21-
export const useInstallationConfiguration = () => {
22-
return useQuery({
23-
queryKey: ["installation-configuration"],
24-
staleTime: 1000 * 60 * 10, // 10 minute
25-
queryFn: async () => {
26-
const response = await installationClient.getInstallationConfiguration({});
27-
return response.configuration;
28-
},
29-
});
30-
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) 2025 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 { useQuery } from "@tanstack/react-query";
8+
import { installationClient } from "../../service/public-api";
9+
10+
export const useInstallationConfiguration = () => {
11+
return useQuery({
12+
queryKey: ["installation-configuration"],
13+
staleTime: 1000 * 60 * 30, // 30 minutes
14+
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
15+
queryFn: async () => {
16+
const response = await installationClient.getInstallationConfiguration({});
17+
return response.configuration;
18+
},
19+
});
20+
};

components/dashboard/src/dedicated-setup/use-needs-setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import { useQuery } from "@tanstack/react-query";
88
import { noPersistence } from "../data/setup";
99
import { installationClient } from "../service/public-api";
1010
import { GetOnboardingStateRequest } from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
11-
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";
11+
import { useInstallationConfiguration } from "../data/installation/installation-config-query";
1212

1313
/**
14-
* @description Returns a flage stating if the current installation still needs setup before it can be used. Also returns an isLoading indicator as the check is async
14+
* @description Returns a flag stating if the current installation still needs setup before it can be used. Also returns an isLoading indicator as the check is async
1515
*/
1616
export const useNeedsSetup = () => {
1717
const { data: onboardingState, isLoading } = useOnboardingState();

components/dashboard/src/dedicated-setup/use-show-dedicated-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { useQueryParams } from "../hooks/use-query-params";
88
import { useCallback, useState } from "react";
99
import { isCurrentHostExcludedFromSetup, useNeedsSetup } from "./use-needs-setup";
10-
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";
10+
import { useInstallationConfiguration } from "../data/installation/installation-config-query";
1111

1212
const FORCE_SETUP_PARAM = "dedicated-setup";
1313
const FORCE_SETUP_PARAM_VALUE = "force";

components/dashboard/src/insights/download/DownloadInsights.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const DownloadInsightsToast = ({ organizationId, from, to, organizationNa
5252
if (isLoading) {
5353
return (
5454
<div>
55-
<span>Preparing usage export</span>
55+
<span>Preparing insights export</span>
5656
<br />
5757
<span className="text-sm">Exporting page {progress}</span>
5858
</div>
@@ -64,15 +64,15 @@ export const DownloadInsightsToast = ({ organizationId, from, to, organizationNa
6464
<div className="flex flex-row items-start space-x-2">
6565
<AlertTriangle className="w-5 h-5 mt-0.5" />
6666
<div>
67-
<span>Error exporting your usage data:</span>
67+
<span>Error exporting your insights data:</span>
6868
<pre className="mt-2 whitespace-normal text-sm">{error.message}</pre>
6969
</div>
7070
</div>
7171
);
7272
}
7373

7474
if (!data || !data.blob || data.count === 0) {
75-
return <span>No usage data for the selected period.</span>;
75+
return <span>No insights data for the selected period.</span>;
7676
}
7777

7878
const readableSize = prettyBytes(data.blob.size);
@@ -81,7 +81,7 @@ export const DownloadInsightsToast = ({ organizationId, from, to, organizationNa
8181
return (
8282
<div className="flex flex-row items-start justify-between space-x-2">
8383
<div>
84-
<span>Usage export complete.</span>
84+
<span>Insights export complete.</span>
8585
<p className="dark:text-gray-500">
8686
{readableSize} &middot; {formattedCount} {data.count !== 1 ? "entries" : "entry"} exported
8787
</p>

components/dashboard/src/insights/download/download-sessions.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,20 @@ type Args = Pick<ListWorkspaceSessionsRequest, "organizationId" | "from" | "to">
6767
onProgress?: (percentage: number) => void;
6868
};
6969

70-
export type DownloadUsageCSVResponse = {
70+
export type DownloadInsightsCSVResponse = {
7171
blob: Blob | null;
7272
filename: string;
7373
count: number;
7474
};
7575

76-
const downloadUsageCSV = async ({
76+
const downloadInsightsCSV = async ({
7777
organizationId,
7878
from,
7979
to,
8080
organizationName,
8181
signal,
8282
onProgress,
83-
}: Args): Promise<DownloadUsageCSVResponse> => {
83+
}: Args): Promise<DownloadInsightsCSVResponse> => {
8484
const start = dayjs(from?.toDate()).format("YYYYMMDD");
8585
const end = dayjs(to?.toDate()).format("YYYYMMDD");
8686
const filename = `gitpod-sessions-${organizationName}-${start}-${end}.csv`;
@@ -197,16 +197,16 @@ export const transformSessionRecord = (session: WorkspaceSession) => {
197197

198198
export const useDownloadSessionsCSV = (args: Args) => {
199199
const client = useQueryClient();
200-
const key = getDownloadUsageCSVQueryKey(args);
200+
const key = getDownloadInsightsCSVQueryKey(args);
201201

202202
const abort = useCallback(() => {
203203
client.removeQueries([key]);
204204
}, [client, key]);
205205

206-
const query = useQuery<DownloadUsageCSVResponse, Error>(
206+
const query = useQuery<DownloadInsightsCSVResponse, Error>(
207207
key,
208208
async ({ signal }) => {
209-
return downloadUsageCSV({ ...args, signal });
209+
return downloadInsightsCSV({ ...args, signal });
210210
},
211211
{
212212
retry: false,
@@ -221,6 +221,6 @@ export const useDownloadSessionsCSV = (args: Args) => {
221221
};
222222
};
223223

224-
const getDownloadUsageCSVQueryKey = (args: Args) => {
225-
return noPersistence(["usage-export", args]);
224+
const getDownloadInsightsCSVQueryKey = (args: Args) => {
225+
return noPersistence(["insights-export", args]);
226226
};

components/dashboard/src/menu/Menu.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import { Separator } from "../components/Separator";
1515
import PillMenuItem from "../components/PillMenuItem";
1616
import { PaymentContext } from "../payment-context";
1717
import FeedbackFormModal from "../feedback-form/FeedbackModal";
18-
import { isGitpodIo } from "../utils";
1918
import OrganizationSelector from "./OrganizationSelector";
2019
import { getAdminTabs } from "../admin/admin.routes";
2120
import classNames from "classnames";
2221
import { User, RoleOrPermission } from "@gitpod/public-api/lib/gitpod/v1/user_pb";
2322
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
2423
import { ConfigurationsMigrationCoachmark } from "../repositories/coachmarks/MigrationCoachmark";
24+
import { useInstallationConfiguration } from "../data/installation/installation-config-query";
2525

2626
interface Entry {
2727
title: string;
@@ -35,6 +35,9 @@ export default function Menu() {
3535
const { setCurrency } = useContext(PaymentContext);
3636
const [isFeedbackFormVisible, setFeedbackFormVisible] = useState<boolean>(false);
3737

38+
const { data: installationConfig, isLoading: isInstallationConfigLoading } = useInstallationConfiguration();
39+
const isGitpodIo = isInstallationConfigLoading ? false : !installationConfig?.isDedicatedInstallation;
40+
3841
useEffect(() => {
3942
const { server } = getGitpodService();
4043
server.getClientRegion().then((v) => {
@@ -95,7 +98,7 @@ export default function Menu() {
9598
/>
9699
</li>
97100
)}
98-
{isGitpodIo() && (
101+
{isGitpodIo && (
99102
<li className="cursor-pointer">
100103
<PillMenuItem name="Feedback" onClick={handleFeedbackFormClick} />
101104
</li>
@@ -163,6 +166,9 @@ type UserMenuProps = {
163166
onFeedback?: () => void;
164167
};
165168
const UserMenu: FC<UserMenuProps> = ({ user, className, withAdminLink, withFeedbackLink, onFeedback }) => {
169+
const { data: installationConfig, isLoading: isInstallationConfigLoading } = useInstallationConfiguration();
170+
const isGitpodIo = isInstallationConfigLoading ? false : !installationConfig?.isDedicatedInstallation;
171+
166172
const extraSection = useMemo(() => {
167173
const items: ContextMenuEntry[] = [];
168174

@@ -172,7 +178,7 @@ const UserMenu: FC<UserMenuProps> = ({ user, className, withAdminLink, withFeedb
172178
link: "/admin",
173179
});
174180
}
175-
if (withFeedbackLink && isGitpodIo()) {
181+
if (withFeedbackLink && isGitpodIo) {
176182
items.push({
177183
title: "Feedback",
178184
onClick: onFeedback,
@@ -185,7 +191,7 @@ const UserMenu: FC<UserMenuProps> = ({ user, className, withAdminLink, withFeedb
185191
}
186192

187193
return items;
188-
}, [onFeedback, user?.rolesOrPermissions, withAdminLink, withFeedbackLink]);
194+
}, [isGitpodIo, onFeedback, user?.rolesOrPermissions, withAdminLink, withFeedbackLink]);
189195

190196
const menuEntries = useMemo(() => {
191197
return [

components/dashboard/src/menu/OrganizationSelector.tsx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query";
1414
import { useIsOwner, useListOrganizationMembers, useHasRolePermission } from "../data/organizations/members-query";
1515
import { isAllowedToCreateOrganization } from "@gitpod/public-api-common/lib/user-utils";
1616
import { OrganizationRole } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
17-
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";
1817
import { useFeatureFlag } from "../data/featureflag-query";
1918
import { PlusIcon } from "lucide-react";
19+
import { useInstallationConfiguration } from "../data/installation/installation-config-query";
2020

2121
export default function OrganizationSelector() {
2222
const user = useCurrentUser();
2323
const orgs = useOrganizations();
2424
const currentOrg = useCurrentOrg();
2525
const members = useListOrganizationMembers().data ?? [];
26-
const owner = useIsOwner();
26+
const isOwner = useIsOwner();
2727
const hasMemberPermission = useHasRolePermission(OrganizationRole.MEMBER);
2828
const { data: billingMode } = useOrgBillingMode();
2929
const getOrgURL = useGetOrgURL();
@@ -77,15 +77,27 @@ export default function OrganizationSelector() {
7777
separator: true,
7878
link: "/members",
7979
});
80-
linkEntries.push({
81-
title: "Usage",
82-
customContent: <LinkEntry>Usage</LinkEntry>,
83-
active: false,
84-
separator: false,
85-
link: "/usage",
86-
});
80+
if (isDedicated) {
81+
if (isOwner) {
82+
linkEntries.push({
83+
title: "Insights",
84+
customContent: <LinkEntry>Insights</LinkEntry>,
85+
active: false,
86+
separator: false,
87+
link: "/insights",
88+
});
89+
}
90+
} else {
91+
linkEntries.push({
92+
title: "Usage",
93+
customContent: <LinkEntry>Usage</LinkEntry>,
94+
active: false,
95+
separator: false,
96+
link: "/usage",
97+
});
98+
}
8799
// Show billing if user is an owner of current org
88-
if (owner) {
100+
if (isOwner) {
89101
if (billingMode?.mode === "usage-based") {
90102
linkEntries.push({
91103
title: "Billing",

components/dashboard/src/teams/OrgSettingsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useCurrentOrg } from "../data/organizations/orgs-query";
1515
import { useFeatureFlag } from "../data/featureflag-query";
1616
import { Organization } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
1717
import { useIsOwner } from "../data/organizations/members-query";
18-
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";
18+
import { useInstallationConfiguration } from "../data/installation/installation-config-query";
1919

2020
export interface OrgSettingsPageProps {
2121
children: React.ReactNode;

components/dashboard/src/teams/policies/MaxParallelWorkspaces.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { NumberInput } from "../../components/forms/TextInputField";
1313
import { LoadingButton } from "@podkit/buttons/LoadingButton";
1414
import { MAX_PARALLEL_WORKSPACES_FREE, MAX_PARALLEL_WORKSPACES_PAID } from "@gitpod/gitpod-protocol";
1515
import { PlainMessage } from "@bufbuild/protobuf";
16-
import { useInstallationConfiguration } from "../../data/installation/default-workspace-image-query";
16+
import { useInstallationConfiguration } from "../../data/installation/installation-config-query";
1717

1818
type Props = {
1919
isOwner: boolean;

components/dashboard/src/user-settings/PageWithSettingsSubMenu.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { PageWithSubMenu } from "../components/PageWithSubMenu";
8+
import { useInstallationConfiguration } from "../data/installation/installation-config-query";
89
import {
910
settingsPathAccount,
1011
settingsPathIntegrations,
@@ -23,24 +24,31 @@ export interface PageWithAdminSubMenuProps {
2324
}
2425

2526
export function PageWithSettingsSubMenu({ children }: PageWithAdminSubMenuProps) {
26-
const settingsMenu = getSettingsMenu();
27+
const settingsMenu = useUserSettingsMenu();
2728
return (
2829
<PageWithSubMenu subMenu={settingsMenu} title="User Settings" subtitle="Manage your personal account settings.">
2930
{children}
3031
</PageWithSubMenu>
3132
);
3233
}
3334

34-
function getSettingsMenu() {
35+
function useUserSettingsMenu() {
36+
const { data: installationConfig } = useInstallationConfiguration();
37+
const isGitpodIo = installationConfig?.isDedicatedInstallation === false;
38+
3539
return [
3640
{
3741
title: "Account",
3842
link: [settingsPathAccount, settingsPathMain],
3943
},
40-
{
41-
title: "Notifications",
42-
link: [settingsPathNotifications],
43-
},
44+
...(isGitpodIo
45+
? [
46+
{
47+
title: "Notifications",
48+
link: [settingsPathNotifications],
49+
},
50+
]
51+
: []),
4452
{
4553
title: "Variables",
4654
link: [settingsPathVariables],

0 commit comments

Comments
 (0)