Skip to content
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

Organization onboarding welcome message #20577

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3c62068
WIP
filiptronicek Jan 31, 2025
99b0b71
Backend iteration
filiptronicek Feb 3, 2025
d42f22d
Tweak design
filiptronicek Feb 3, 2025
0d7d880
Update organization onboarding settings to support optional welcome m…
filiptronicek Feb 3, 2025
6293167
Implement new user onboarding flow with welcome message
filiptronicek Feb 3, 2025
3a05bca
Refactor welcome message components and update dependencies
filiptronicek Feb 4, 2025
f3266bc
Fix localStorage and Markdown styling in dashboard components
filiptronicek Feb 4, 2025
0b3eda8
Merge remote-tracking branch 'origin/main' into ft/org-welcome-msg
filiptronicek Feb 4, 2025
aff357b
Fix build issues
filiptronicek Feb 5, 2025
5009281
Improve welcome message configuration with error handling
filiptronicek Feb 5, 2025
9b13421
Properly do partial org opts
filiptronicek Feb 5, 2025
b216fa4
Remove suggested repository management from repository list
filiptronicek Feb 5, 2025
e6ee954
Proper partial updating for `onboardingSettings`
filiptronicek Feb 5, 2025
00c1213
center view button
filiptronicek Feb 5, 2025
ef10501
Don't overshare
filiptronicek Feb 5, 2025
cf3124b
update comments
filiptronicek Feb 5, 2025
3d8f8b0
Small drive-by for insights export toast
filiptronicek Feb 6, 2025
b7353c3
Colored suggestions
filiptronicek Feb 6, 2025
b4149c6
Update protobuf import paths for organization onboarding settings and…
filiptronicek Feb 7, 2025
ac0e2c5
Merge remote-tracking branch 'origin/main' into ft/org-welcome-msg
filiptronicek Feb 7, 2025
004e07a
fix org service test expecting member list to include admin
filiptronicek Feb 7, 2025
ba2538d
Fix OIDC callback to properly append newUser query parameter
filiptronicek Feb 7, 2025
0bad6df
Fix test checks
filiptronicek Feb 7, 2025
f601310
Merge remote-tracking branch 'origin/main' into ft/org-welcome-msg
filiptronicek Feb 10, 2025
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
1 change: 1 addition & 0 deletions components/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"react-focus-on": "^3.8.1",
"react-intl-tel-input": "^8.2.0",
"react-linkedin-login-oauth2": "^2.0.1",
"react-markdown": "^9.0.3",
"react-popper": "^2.3.0",
"react-portal": "^4.2.2",
"react-router-dom": "^5.2.0",
Expand Down
18 changes: 16 additions & 2 deletions components/dashboard/public/complete-auth/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,35 @@
-->

<html>

<head>
<meta charset='utf-8'>
<title>Done</title>
<script>
if (window.opener) {
const search = new URLSearchParams(window.location.search);
const message = search.get("message");
window.opener.postMessage(message, `https://${window.location.hostname}`);
const newUser = search.get("newUser");
window.opener.postMessage(message + "&newUser=" + newUser, `https://${window.location.hostname}`);

if (newUser === "true") {
try {
localStorage.setItem("newUserOnboardingPending", newUser);
} catch (e) {
console.error("Failed to set localStorage item", e);
}
}
} else {
console.log("This page is supposed to be opened by Gitpod.")
setTimeout("window.close();", 1000);
}
</script>
</head>

<body>
If this tab is not closed automatically, feel free to close it and proceed. <button className="px-4 py-2 my-auto bg-gray-900 hover:bg-gray-800 dark:bg-kumquat-base dark:hover:bg-kumquat-ripe text-gray-50 dark:text-gray-900 text-sm font-medium rounded-xl focus:outline-none focus:ring transition ease-in-out" type="button" onclick="window.open('', '_self', ''); window.close();">Close</button>
If this tab is not closed automatically, feel free to close it and proceed. <button
className="px-4 py-2 my-auto bg-gray-900 hover:bg-gray-800 dark:bg-kumquat-base dark:hover:bg-kumquat-ripe text-gray-50 dark:text-gray-900 text-sm font-medium rounded-xl focus:outline-none focus:ring transition ease-in-out"
type="button" onclick="window.open('', '_self', ''); window.close();">Close</button>
</body>

</html>
4 changes: 3 additions & 1 deletion components/dashboard/src/components/forms/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ type Props = {
topMargin?: boolean;
className?: string;
disabled?: boolean;
labelHidden?: boolean;
};

export const InputField: FunctionComponent<Props> = memo(
({ label, id, hint, error, topMargin = true, className, children, disabled = false }) => {
({ label, id, hint, error, topMargin = true, className, children, disabled = false, labelHidden = false }) => {
return (
<div className={cn("flex flex-col space-y-2", { "mt-4": topMargin }, className)}>
{label && (
<label
className={cn(
"text-md font-semibold",
{ "sr-only": labelHidden },
disabled
? "text-gray-400 dark:text-gray-400"
: error
Expand Down
27 changes: 27 additions & 0 deletions components/dashboard/src/components/podkit/forms/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 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 * as React from "react";

import { cn } from "@podkit/lib/cn";

const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-pk-surface-primary px-3 py-2 text-base ring-offset-background placeholder:text-pk-content-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Textarea.displayName = "Textarea";

export { Textarea };
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ import { useOrgWorkspaceClassesQueryInvalidator } from "./org-workspace-classes-
import { PlainMessage } from "@bufbuild/protobuf";
import { useOrgRepoSuggestionsInvalidator } from "./suggested-repositories-query";

type UpdateOrganizationSettingsArgs = Partial<
Pick<
PlainMessage<OrganizationSettings>,
| "workspaceSharingDisabled"
| "defaultWorkspaceImage"
| "allowedWorkspaceClasses"
| "pinnedEditorVersions"
| "restrictedEditorNames"
| "defaultRole"
| "timeoutSettings"
| "roleRestrictions"
| "maxParallelRunningWorkspaces"
| "onboardingSettings"
| "annotateGitCommits"
>
export type UpdateOrganizationSettingsArgs = Partial<
Omit<
Pick<
PlainMessage<OrganizationSettings>,
| "workspaceSharingDisabled"
| "defaultWorkspaceImage"
| "allowedWorkspaceClasses"
| "pinnedEditorVersions"
| "restrictedEditorNames"
| "defaultRole"
| "timeoutSettings"
| "roleRestrictions"
| "maxParallelRunningWorkspaces"
| "annotateGitCommits"
>,
never
> & {
onboardingSettings?: Partial<PlainMessage<OrganizationSettings>["onboardingSettings"]>; // this enables us to not have to specify all of the onboarding settings on every update
}
>;

export const useUpdateOrgSettingsMutation = () => {
Expand Down Expand Up @@ -66,7 +70,10 @@ export const useUpdateOrgSettingsMutation = () => {
roleRestrictions,
updateRoleRestrictions: !!roleRestrictions,
maxParallelRunningWorkspaces,
onboardingSettings,
onboardingSettings: {
...onboardingSettings,
updateRecommendedRepositories: !!onboardingSettings?.recommendedRepositories,
},
annotateGitCommits,
});
return settings.settings!;
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
margin: 0 auto !important;
}
p {
@apply text-sm text-gray-400 dark:text-gray-600;
@apply text-sm text-pk-content-secondary;
}

.app-container {
Expand All @@ -118,7 +118,7 @@
}

code {
@apply bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded-md text-sm font-mono font-medium;
@apply bg-gray-100 dark:bg-gray-800 text-pk-content-primary px-1.5 py-0.5 rounded-md text-sm font-mono font-medium;
}

textarea,
Expand Down
7 changes: 3 additions & 4 deletions components/dashboard/src/login/SSOLoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ import { useOnboardingState } from "../dedicated-setup/use-needs-setup";
import { getOrgSlugFromQuery } from "../data/organizations/orgs-query";
import { storageAvailable } from "../utils";

type Props = {
onSuccess: () => void;
};

function getOrgSlugFromPath(path: string) {
// '/login/acme' => ['', 'login', 'acme']
const pathSegments = path.split("/");
Expand All @@ -29,6 +25,9 @@ function getOrgSlugFromPath(path: string) {
return pathSegments[2];
}

type Props = {
onSuccess: () => void;
};
export const SSOLoginForm: FC<Props> = ({ onSuccess }) => {
const location = useLocation();
const { data: onboardingState } = useOnboardingState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export const ManageRepoSuggestion: FC<Props> = ({ configuration }) => {
await updateTeamSettings.mutateAsync(
{
onboardingSettings: {
...orgSettings?.onboardingSettings,
recommendedRepositories: [...newRepositories],
},
},
Expand All @@ -47,7 +46,7 @@ export const ManageRepoSuggestion: FC<Props> = ({ configuration }) => {
},
);
},
[orgSettings?.onboardingSettings, toast, updateTeamSettings],
[orgSettings?.onboardingSettings?.recommendedRepositories, toast, updateTeamSettings],
);

const isSuggested = orgSettings?.onboardingSettings?.recommendedRepositories?.includes(configuration.id);
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/start/StartWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function parseParameters(search?: string): { notFound?: boolean } {

export interface StartWorkspaceState {
/**
* This is set to the istanceId we started (think we started on).
* This is set to the instanceId we started (think we started on).
* We only receive updates for this particular instance, or none if not set.
*/
startedInstanceId?: string;
Expand Down
23 changes: 15 additions & 8 deletions components/dashboard/src/teams/TeamOnboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
* 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 {
UpdateOrganizationSettingsArgs,
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";
Expand All @@ -24,11 +25,16 @@ import { useOrgSuggestedRepos } from "../data/organizations/suggested-repositori
import { RepositoryListItem } from "../repositories/list/RepoListItem";
import { LoadingState } from "@podkit/loading/LoadingState";
import { Table, TableHeader, TableRow, TableHead, TableBody } from "@podkit/tables/Table";
import { WelcomeMessageConfigurationField } from "./onboarding/WelcomeMessageConfigurationField";

export type UpdateTeamSettingsOptions = {
throwMutateError?: boolean;
};

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

const { data: settings } = useOrgSettingsQuery();
Expand All @@ -39,7 +45,7 @@ export default function TeamOnboardingPage() {
const [internalLink, setInternalLink] = useState<string | undefined>(undefined);

const handleUpdateTeamSettings = useCallback(
async (newSettings: Partial<PlainMessage<OrganizationSettings>>, options?: { throwMutateError?: boolean }) => {
async (newSettings: UpdateOrganizationSettingsArgs, options?: UpdateTeamSettingsOptions) => {
if (!org?.id) {
throw new Error("no organization selected");
}
Expand Down Expand Up @@ -70,11 +76,10 @@ export default function TeamOnboardingPage() {
await handleUpdateTeamSettings({
onboardingSettings: {
internalLink,
recommendedRepositories: settings?.onboardingSettings?.recommendedRepositories ?? [],
},
});
},
[handleUpdateTeamSettings, internalLink, settings?.onboardingSettings?.recommendedRepositories],
[handleUpdateTeamSettings, internalLink],
);

useEffect(() => {
Expand Down Expand Up @@ -146,7 +151,7 @@ export default function TeamOnboardingPage() {
</TableRow>
</TableHeader>
<TableBody>
{(suggestedRepos ?? []).map((repo) => (
{suggestedRepos?.map((repo) => (
<RepositoryListItem
key={repo.configurationId}
configuration={repo.configuration}
Expand All @@ -157,6 +162,8 @@ export default function TeamOnboardingPage() {
</Table>
)}
</ConfigurationSettingsField>

<WelcomeMessageConfigurationField handleUpdateTeamSettings={handleUpdateTeamSettings} />
</div>
</OrgSettingsPage>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* 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 { useState } from "react";
import { useListOrganizationMembers } from "../../data/organizations/members-query";

import type { OrganizationSettings_OnboardingSettings_WelcomeMessage } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
import { Button } from "@podkit/buttons/Button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@podkit/dropdown/DropDown";

type Props = {
settings: OrganizationSettings_OnboardingSettings_WelcomeMessage | undefined;
setFeaturedMemberId: (featuredMemberId: string | undefined) => void;
};
export const OrgMemberAvatarInput = ({ settings, setFeaturedMemberId }: Props) => {
const { data: members } = useListOrganizationMembers();

const [avatarUrl, setAvatarUrl] = useState<string | undefined>(settings?.featuredMemberResolvedAvatarUrl);

return (
<DropdownMenu>
<DropdownMenuTrigger>
<div className="flex flex-col justify-center items-center gap-2">
{avatarUrl ? (
<img src={avatarUrl} alt="" className="w-16 h-16 rounded-full" />
) : (
<div className="w-16 h-16 rounded-full bg-[#EA71DE]" />
)}
<Button variant="secondary" size="sm" type="button">
Change Photo
</Button>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
key="disabled"
onClick={() => {
setFeaturedMemberId(undefined);
setAvatarUrl(undefined);
}}
>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded-full bg-pk-surface-tertiary" />
Disable image
</div>
</DropdownMenuItem>
{members?.map((member) => (
<DropdownMenuItem
key={member.userId}
onClick={() => {
setFeaturedMemberId(member.userId);
setAvatarUrl(member.avatarUrl);
}}
>
<div className="flex items-center gap-2">
<img src={member.avatarUrl} alt={member.fullName} className="w-4 h-4 rounded-full" />
{member.fullName}
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
};
Loading
Loading