Skip to content

Commit d54bd04

Browse files
Enteprise onboarding settings (#20508)
* UI reorg * generated stuffs * Make it woooooooork * Classier placeholder * Remove unneeded fragment * Introduce `enterprise_onboarding_enabled` flag * move things properly * add ipv6 localhost
1 parent 663fcb9 commit d54bd04

File tree

261 files changed

+2866
-1371
lines changed

Some content is hidden

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

261 files changed

+2866
-1371
lines changed

components/dashboard/src/app/AppRoutes.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const TeamUsageBasedBilling = React.lazy(() => import(/* webpackPrefetch: true *
6060
const SSO = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/SSO"));
6161
const TeamGitIntegrations = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/GitIntegrationsPage"));
6262
const TeamPolicies = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamPolicies"));
63+
const TeamOnboarding = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamOnboarding"));
6364
const TeamNetworking = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamNetworking"));
6465
const TeamAuthentication = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamAuthentication"));
6566
const InstallGitHubApp = React.lazy(() => import(/* webpackPrefetch: true */ "../projects/InstallGitHubApp"));
@@ -198,6 +199,7 @@ export const AppRoutes = () => {
198199
<Route exact path="/settings" component={TeamSettings} />
199200
<Route exact path="/settings/git" component={TeamGitIntegrations} />
200201
<Route exact path="/settings/policy" component={TeamPolicies} />
202+
<Route exact path="/settings/onboarding" component={TeamOnboarding} />
201203
<Route exact path="/settings/networking" component={TeamNetworking} />
202204
<Route exact path="/settings/auth" component={TeamAuthentication} />
203205
{/* TODO: migrate other org settings pages underneath /settings prefix so we can utilize nested routes */}

components/dashboard/src/data/featureflag-query.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const featureFlags = {
2424
showBrowserExtensionPromotion: false,
2525
enable_experimental_jbtb: false,
2626
enabled_configuration_prebuild_full_clone: false,
27+
enterprise_onboarding_enabled: false,
2728
};
2829

2930
type FeatureFlags = typeof featureFlags;

components/dashboard/src/data/organizations/update-org-settings-mutation.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type UpdateOrganizationSettingsArgs = Partial<
2525
| "timeoutSettings"
2626
| "roleRestrictions"
2727
| "maxParallelRunningWorkspaces"
28+
| "onboardingSettings"
2829
>
2930
>;
3031

@@ -45,6 +46,7 @@ export const useUpdateOrgSettingsMutation = () => {
4546
timeoutSettings,
4647
roleRestrictions,
4748
maxParallelRunningWorkspaces,
49+
onboardingSettings,
4850
}) => {
4951
const settings = await organizationClient.updateOrganizationSettings({
5052
organizationId: teamId,
@@ -60,6 +62,7 @@ export const useUpdateOrgSettingsMutation = () => {
6062
roleRestrictions,
6163
updateRoleRestrictions: !!roleRestrictions,
6264
maxParallelRunningWorkspaces,
65+
onboardingSettings,
6366
});
6467
return settings.settings!;
6568
},

components/dashboard/src/teams/OrgSettingsPage.tsx

+25-4
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 { isGitpodIo } from "../utils";
18+
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";
1919

2020
export interface OrgSettingsPageProps {
2121
children: React.ReactNode;
@@ -27,6 +27,9 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
2727
const orgBillingMode = useOrgBillingMode();
2828
const oidcServiceEnabled = useFeatureFlag("oidcServiceEnabled");
2929
const orgGitAuthProviders = useFeatureFlag("orgGitAuthProviders");
30+
const isOnboardingEnabled = useFeatureFlag("enterprise_onboarding_enabled");
31+
const { data: installationConfig } = useInstallationConfiguration();
32+
const isDedicatedInstallation = !!installationConfig?.isDedicatedInstallation;
3033

3134
const menu = useMemo(
3235
() =>
@@ -36,8 +39,18 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
3639
ssoEnabled: oidcServiceEnabled,
3740
orgGitAuthProviders,
3841
isOwner,
42+
isDedicatedInstallation,
43+
showOnboarding: isOnboardingEnabled && isDedicatedInstallation,
3944
}),
40-
[org.data, orgBillingMode.data, oidcServiceEnabled, orgGitAuthProviders, isOwner],
45+
[
46+
org.data,
47+
orgBillingMode.data,
48+
oidcServiceEnabled,
49+
orgGitAuthProviders,
50+
isOwner,
51+
isDedicatedInstallation,
52+
isOnboardingEnabled,
53+
],
4154
);
4255

4356
const title = "Organization Settings";
@@ -76,8 +89,10 @@ function getOrgSettingsMenu(params: {
7689
ssoEnabled?: boolean;
7790
orgGitAuthProviders: boolean;
7891
isOwner?: boolean;
92+
showOnboarding?: boolean;
93+
isDedicatedInstallation?: boolean;
7994
}) {
80-
const { billingMode, ssoEnabled, orgGitAuthProviders, isOwner } = params;
95+
const { billingMode, ssoEnabled, orgGitAuthProviders, isOwner, showOnboarding, isDedicatedInstallation } = params;
8196
const result = [
8297
{
8398
title: "General",
@@ -88,7 +103,7 @@ function getOrgSettingsMenu(params: {
88103
link: [`/settings/policy`],
89104
},
90105
];
91-
if (isGitpodIo()) {
106+
if (!isDedicatedInstallation) {
92107
result.push(
93108
{
94109
title: "Networking",
@@ -100,6 +115,12 @@ function getOrgSettingsMenu(params: {
100115
},
101116
);
102117
}
118+
if (showOnboarding) {
119+
result.push({
120+
title: "Onboarding",
121+
link: [`/settings/onboarding`],
122+
});
123+
}
103124
if (isOwner && ssoEnabled) {
104125
result.push({
105126
title: "SSO",

components/dashboard/src/teams/TeamNetworking.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
import { isGitpodIo } from "../utils";
8-
import React from "react";
98
import { Heading2, Heading3, Subheading } from "../components/typography/headings";
109
import { OrgSettingsPage } from "./OrgSettingsPage";
1110
import { ConfigurationSettingsField } from "../repositories/detail/ConfigurationSettingsField";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
8+
import { FormEvent, useCallback, useEffect, useState } from "react";
9+
import { Heading2, Heading3, Subheading } from "../components/typography/headings";
10+
import { useIsOwner } from "../data/organizations/members-query";
11+
import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";
12+
import { useCurrentOrg } from "../data/organizations/orgs-query";
13+
import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation";
14+
import { OrgSettingsPage } from "./OrgSettingsPage";
15+
import { ConfigurationSettingsField } from "../repositories/detail/ConfigurationSettingsField";
16+
import { useDocumentTitle } from "../hooks/use-document-title";
17+
import { useToast } from "../components/toasts/Toasts";
18+
import type { PlainMessage } from "@bufbuild/protobuf";
19+
import { InputField } from "../components/forms/InputField";
20+
import { TextInput } from "../components/forms/TextInputField";
21+
import { LoadingButton } from "@podkit/buttons/LoadingButton";
22+
23+
export default function TeamOnboardingPage() {
24+
useDocumentTitle("Organization Settings - Onboarding");
25+
const { toast } = useToast();
26+
const org = useCurrentOrg().data;
27+
const isOwner = useIsOwner();
28+
29+
const { data: settings } = useOrgSettingsQuery();
30+
const updateTeamSettings = useUpdateOrgSettingsMutation();
31+
32+
const [internalLink, setInternalLink] = useState<string | undefined>(undefined);
33+
34+
const handleUpdateTeamSettings = useCallback(
35+
async (newSettings: Partial<PlainMessage<OrganizationSettings>>, options?: { throwMutateError?: boolean }) => {
36+
if (!org?.id) {
37+
throw new Error("no organization selected");
38+
}
39+
if (!isOwner) {
40+
throw new Error("no organization settings change permission");
41+
}
42+
try {
43+
await updateTeamSettings.mutateAsync({
44+
...settings,
45+
...newSettings,
46+
});
47+
toast("Organization settings updated");
48+
} catch (error) {
49+
if (options?.throwMutateError) {
50+
throw error;
51+
}
52+
toast(`Failed to update organization settings: ${error.message}`);
53+
console.error(error);
54+
}
55+
},
56+
[updateTeamSettings, org?.id, isOwner, settings, toast],
57+
);
58+
59+
const handleUpdateInternalLink = useCallback(
60+
async (e: FormEvent) => {
61+
e.preventDefault();
62+
63+
await handleUpdateTeamSettings({ onboardingSettings: { internalLink } });
64+
},
65+
[handleUpdateTeamSettings, internalLink],
66+
);
67+
68+
useEffect(() => {
69+
if (settings) {
70+
setInternalLink(settings.onboardingSettings?.internalLink);
71+
}
72+
}, [settings]);
73+
74+
return (
75+
<OrgSettingsPage>
76+
<div className="space-y-8">
77+
<div>
78+
<Heading2>Policies</Heading2>
79+
<Subheading>Restrict workspace classes, editors and sharing across your organization.</Subheading>
80+
</div>
81+
<ConfigurationSettingsField>
82+
<Heading3>Internal dashboard</Heading3>
83+
<Subheading>
84+
The link to your internal dashboard. This link will be shown to your organization members during
85+
the onboarding process. You can disable showing a link by leaving this field empty.
86+
</Subheading>
87+
<form onSubmit={handleUpdateInternalLink}>
88+
<InputField label="Internal dashboard link" error={undefined} className="mb-4">
89+
<TextInput
90+
value={internalLink}
91+
type="url"
92+
placeholder="https://en.wikipedia.org/wiki/Heisenbug"
93+
onChange={setInternalLink}
94+
disabled={updateTeamSettings.isLoading || !isOwner}
95+
/>
96+
</InputField>
97+
<LoadingButton type="submit" loading={updateTeamSettings.isLoading} disabled={!isOwner}>
98+
Save
99+
</LoadingButton>
100+
</form>
101+
</ConfigurationSettingsField>
102+
</div>
103+
</OrgSettingsPage>
104+
);
105+
}

0 commit comments

Comments
 (0)