Skip to content

Enteprise onboarding settings #20508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const TeamUsageBasedBilling = React.lazy(() => import(/* webpackPrefetch: true *
const SSO = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/SSO"));
const TeamGitIntegrations = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/GitIntegrationsPage"));
const TeamPolicies = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamPolicies"));
const TeamOnboarding = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamOnboarding"));
const TeamNetworking = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamNetworking"));
const TeamAuthentication = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/TeamAuthentication"));
const InstallGitHubApp = React.lazy(() => import(/* webpackPrefetch: true */ "../projects/InstallGitHubApp"));
Expand Down Expand Up @@ -198,6 +199,7 @@ export const AppRoutes = () => {
<Route exact path="/settings" component={TeamSettings} />
<Route exact path="/settings/git" component={TeamGitIntegrations} />
<Route exact path="/settings/policy" component={TeamPolicies} />
<Route exact path="/settings/onboarding" component={TeamOnboarding} />
<Route exact path="/settings/networking" component={TeamNetworking} />
<Route exact path="/settings/auth" component={TeamAuthentication} />
{/* TODO: migrate other org settings pages underneath /settings prefix so we can utilize nested routes */}
Expand Down
1 change: 1 addition & 0 deletions components/dashboard/src/data/featureflag-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const featureFlags = {
showBrowserExtensionPromotion: false,
enable_experimental_jbtb: false,
enabled_configuration_prebuild_full_clone: false,
enterprise_onboarding_enabled: false,
};

type FeatureFlags = typeof featureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type UpdateOrganizationSettingsArgs = Partial<
| "timeoutSettings"
| "roleRestrictions"
| "maxParallelRunningWorkspaces"
| "onboardingSettings"
>
>;

Expand All @@ -45,6 +46,7 @@ export const useUpdateOrgSettingsMutation = () => {
timeoutSettings,
roleRestrictions,
maxParallelRunningWorkspaces,
onboardingSettings,
}) => {
const settings = await organizationClient.updateOrganizationSettings({
organizationId: teamId,
Expand All @@ -60,6 +62,7 @@ export const useUpdateOrgSettingsMutation = () => {
roleRestrictions,
updateRoleRestrictions: !!roleRestrictions,
maxParallelRunningWorkspaces,
onboardingSettings,
});
return settings.settings!;
},
Expand Down
29 changes: 25 additions & 4 deletions components/dashboard/src/teams/OrgSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useCurrentOrg } from "../data/organizations/orgs-query";
import { useFeatureFlag } from "../data/featureflag-query";
import { Organization } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
import { useIsOwner } from "../data/organizations/members-query";
import { isGitpodIo } from "../utils";
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";

export interface OrgSettingsPageProps {
children: React.ReactNode;
Expand All @@ -27,6 +27,9 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
const orgBillingMode = useOrgBillingMode();
const oidcServiceEnabled = useFeatureFlag("oidcServiceEnabled");
const orgGitAuthProviders = useFeatureFlag("orgGitAuthProviders");
const isOnboardingEnabled = useFeatureFlag("enterprise_onboarding_enabled");
const { data: installationConfig } = useInstallationConfiguration();
const isDedicatedInstallation = !!installationConfig?.isDedicatedInstallation;

const menu = useMemo(
() =>
Expand All @@ -36,8 +39,18 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
ssoEnabled: oidcServiceEnabled,
orgGitAuthProviders,
isOwner,
isDedicatedInstallation,
showOnboarding: isOnboardingEnabled && isDedicatedInstallation,
}),
[org.data, orgBillingMode.data, oidcServiceEnabled, orgGitAuthProviders, isOwner],
[
org.data,
orgBillingMode.data,
oidcServiceEnabled,
orgGitAuthProviders,
isOwner,
isDedicatedInstallation,
isOnboardingEnabled,
],
);

const title = "Organization Settings";
Expand Down Expand Up @@ -76,8 +89,10 @@ function getOrgSettingsMenu(params: {
ssoEnabled?: boolean;
orgGitAuthProviders: boolean;
isOwner?: boolean;
showOnboarding?: boolean;
isDedicatedInstallation?: boolean;
}) {
const { billingMode, ssoEnabled, orgGitAuthProviders, isOwner } = params;
const { billingMode, ssoEnabled, orgGitAuthProviders, isOwner, showOnboarding, isDedicatedInstallation } = params;
const result = [
{
title: "General",
Expand All @@ -88,7 +103,7 @@ function getOrgSettingsMenu(params: {
link: [`/settings/policy`],
},
];
if (isGitpodIo()) {
if (!isDedicatedInstallation) {
result.push(
{
title: "Networking",
Expand All @@ -100,6 +115,12 @@ function getOrgSettingsMenu(params: {
},
);
}
if (showOnboarding) {
result.push({
title: "Onboarding",
link: [`/settings/onboarding`],
});
}
if (isOwner && ssoEnabled) {
result.push({
title: "SSO",
Expand Down
1 change: 0 additions & 1 deletion components/dashboard/src/teams/TeamNetworking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

import { isGitpodIo } from "../utils";
import React from "react";
import { Heading2, Heading3, Subheading } from "../components/typography/headings";
import { OrgSettingsPage } from "./OrgSettingsPage";
import { ConfigurationSettingsField } from "../repositories/detail/ConfigurationSettingsField";
Expand Down
105 changes: 105 additions & 0 deletions components/dashboard/src/teams/TeamOnboarding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright (c) 2025 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 { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
import { FormEvent, useCallback, useEffect, useState } from "react";
import { Heading2, Heading3, Subheading } from "../components/typography/headings";
import { useIsOwner } from "../data/organizations/members-query";
import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";
import { useCurrentOrg } from "../data/organizations/orgs-query";
import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation";
import { OrgSettingsPage } from "./OrgSettingsPage";
import { ConfigurationSettingsField } from "../repositories/detail/ConfigurationSettingsField";
import { useDocumentTitle } from "../hooks/use-document-title";
import { useToast } from "../components/toasts/Toasts";
import type { PlainMessage } from "@bufbuild/protobuf";
import { InputField } from "../components/forms/InputField";
import { TextInput } from "../components/forms/TextInputField";
import { LoadingButton } from "@podkit/buttons/LoadingButton";

export default function TeamOnboardingPage() {
useDocumentTitle("Organization Settings - Onboarding");
const { toast } = useToast();
const org = useCurrentOrg().data;
const isOwner = useIsOwner();

const { data: settings } = useOrgSettingsQuery();
const updateTeamSettings = useUpdateOrgSettingsMutation();

const [internalLink, setInternalLink] = useState<string | undefined>(undefined);

const handleUpdateTeamSettings = useCallback(
async (newSettings: Partial<PlainMessage<OrganizationSettings>>, options?: { throwMutateError?: boolean }) => {
if (!org?.id) {
throw new Error("no organization selected");
}
if (!isOwner) {
throw new Error("no organization settings change permission");
}
try {
await updateTeamSettings.mutateAsync({
...settings,
...newSettings,
});
toast("Organization settings updated");
} catch (error) {
if (options?.throwMutateError) {
throw error;
}
toast(`Failed to update organization settings: ${error.message}`);
console.error(error);
}
},
[updateTeamSettings, org?.id, isOwner, settings, toast],
);

const handleUpdateInternalLink = useCallback(
async (e: FormEvent) => {
e.preventDefault();

await handleUpdateTeamSettings({ onboardingSettings: { internalLink } });
},
[handleUpdateTeamSettings, internalLink],
);

useEffect(() => {
if (settings) {
setInternalLink(settings.onboardingSettings?.internalLink);
}
}, [settings]);

return (
<OrgSettingsPage>
<div className="space-y-8">
<div>
<Heading2>Policies</Heading2>
<Subheading>Restrict workspace classes, editors and sharing across your organization.</Subheading>
</div>
<ConfigurationSettingsField>
<Heading3>Internal dashboard</Heading3>
<Subheading>
The link to your internal dashboard. This link will be shown to your organization members during
the onboarding process. You can disable showing a link by leaving this field empty.
</Subheading>
<form onSubmit={handleUpdateInternalLink}>
<InputField label="Internal dashboard link" error={undefined} className="mb-4">
<TextInput
value={internalLink}
type="url"
placeholder="https://en.wikipedia.org/wiki/Heisenbug"
onChange={setInternalLink}
disabled={updateTeamSettings.isLoading || !isOwner}
/>
</InputField>
<LoadingButton type="submit" loading={updateTeamSettings.isLoading} disabled={!isOwner}>
Save
</LoadingButton>
</form>
</ConfigurationSettingsField>
</div>
</OrgSettingsPage>
);
}
Loading
Loading