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

Repo suggestions improvements #20582

Merged
merged 6 commits into from
Feb 7, 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
28 changes: 18 additions & 10 deletions components/dashboard/src/components/RepositoryFinder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ import { useAuthProviderDescriptions } from "../data/auth-providers/auth-provide
import { ReactComponent as Exclamation2 } from "../images/exclamation2.svg";
import { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
import { SuggestedRepository } from "@gitpod/public-api/lib/gitpod/v1/scm_pb";
import { PREDEFINED_REPOS } from "../data/git-providers/predefined-repos";
import { PREDEFINED_REPOS, PredefinedRepo } from "../data/git-providers/predefined-repos";
import { useConfiguration, useListConfigurations } from "../data/configurations/configuration-queries";
import { useUserLoader } from "../hooks/use-user-loader";
import { conjunctScmProviders, getDeduplicatedScmProviders } from "../utils";
import { cn } from "@podkit/lib/cn";
import { useOrgSuggestedRepos } from "../data/organizations/suggested-repositories-query";
import { toRemoteURL } from "../projects/render-utils";

type PredefinedRepoOption = typeof PREDEFINED_REPOS[number];
const isPredefined = (repo: SuggestedRepository | PredefinedRepoOption): boolean => {
const isPredefined = (repo: SuggestedRepository | PredefinedRepo): boolean => {
return (
PREDEFINED_REPOS.some((predefined) => predefined.url === repo.url) &&
!(repo as SuggestedRepository).configurationId
Expand All @@ -41,7 +40,7 @@ const resolveIcon = (contextUrl?: string): string => {
};

type PredefinedRepositoryOptionProps = {
repo: PredefinedRepoOption;
repo: PredefinedRepo;
};
const PredefinedRepositoryOption: FC<PredefinedRepositoryOptionProps> = ({ repo }) => {
const prettyUrl = toRemoteURL(repo.url);
Expand All @@ -50,7 +49,12 @@ const PredefinedRepositoryOption: FC<PredefinedRepositoryOptionProps> = ({ repo
return (
<div className="flex flex-col overflow-hidden" aria-label={`Demo: ${repo.url}`}>
<div className="flex items-center">
<img className={cn("w-5 mr-2 text-pk-content-secondary")} src={icon} alt="" />
{repo.configurationId ? (
<RepositoryIcon className={cn("w-5 mr-2 text-kumquat-ripe")} />
) : (
<img className={cn("w-5 mr-2 text-pk-content-secondary")} src={icon} alt="" />
)}

<span className="text-sm font-semibold">{repo.repoName}</span>
<MiddleDot className="px-0.5 text-pk-content-secondary" />
<span
Expand Down Expand Up @@ -280,6 +284,7 @@ export default function RepositoryFinder({
url: repo.url,
repoName: repo.repoName,
description: "",
configurationId: repo.configurationId,
}));
}

Expand Down Expand Up @@ -317,11 +322,14 @@ export default function RepositoryFinder({
repo.url.toLowerCase().includes(searchString.toLowerCase()) ||
repo.repoName.toLowerCase().includes(searchString.toLowerCase())
) {
result.push({
id: repo.url,
element: <PredefinedRepositoryOption repo={repo} />,
isSelectable: true,
});
const alreadyPresent = result.find((r) => r.id === repo.configurationId);
if (!alreadyPresent) {
result.push({
id: repo.url,
element: <PredefinedRepositoryOption repo={repo} />,
isSelectable: true,
});
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@
* See License.AGPL.txt in the project root for license information.
*/

export const PREDEFINED_REPOS = [
export type PredefinedRepo = {
url: string;
repoName: string;
description: string;
/**
* The configuration ID of the repository.
* This is only set for org-recommended repos.
*/
configurationId?: string;
};

export const PREDEFINED_REPOS: PredefinedRepo[] = [
{
url: "https://github.com/gitpod-demos/voting-app",
repoName: "demo-docker",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const DownloadInsightsToast = ({ organizationId, from, to, organizationNa
return (
<div>
<span>Preparing usage export</span>
Exporting page {progress}
<br />
<span className="text-sm">Exporting page {progress}</span>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FC } from "react";
import { ConfigurationNameForm } from "./general/ConfigurationName";
import { RemoveConfiguration } from "./general/RemoveConfiguration";
import { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import { ManageRepoSuggestion } from "./general/ManageRepoSuggestion";

type Props = {
configuration: Configuration;
Expand All @@ -16,6 +17,7 @@ export const ConfigurationDetailGeneral: FC<Props> = ({ configuration }) => {
return (
<>
<ConfigurationNameForm configuration={configuration} />
<ManageRepoSuggestion configuration={configuration} />
<RemoveConfiguration configuration={configuration} />
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* 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 { SwitchInputField } from "@podkit/switch/Switch";
import { Heading3, Subheading } from "@podkit/typography/Headings";
import { FC, useCallback } from "react";
import { InputField } from "../../../components/forms/InputField";
import PillLabel from "../../../components/PillLabel";
import { useToast } from "../../../components/toasts/Toasts";
import { useOrgSettingsQuery } from "../../../data/organizations/org-settings-query";
import { useUpdateOrgSettingsMutation } from "../../../data/organizations/update-org-settings-mutation";
import { useId } from "../../../hooks/useId";
import { ConfigurationSettingsField } from "../ConfigurationSettingsField";
import { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import { SquareArrowOutUpRight } from "lucide-react";

type Props = {
configuration: Configuration;
};
export const ManageRepoSuggestion: FC<Props> = ({ configuration }) => {
const { data: orgSettings } = useOrgSettingsQuery();
const { toast } = useToast();
const updateTeamSettings = useUpdateOrgSettingsMutation();
const updateRecommendedRepository = useCallback(
async (configurationId: string, suggested: boolean) => {
const newRepositories = new Set(orgSettings?.onboardingSettings?.recommendedRepositories ?? []);
if (suggested) {
newRepositories.add(configurationId);
} else {
newRepositories.delete(configurationId);
}

await updateTeamSettings.mutateAsync(
{
onboardingSettings: {
...orgSettings?.onboardingSettings,
recommendedRepositories: [...newRepositories],
},
},
{
onError: (error) => {
toast(`Failed to update recommended repositories: ${error.message}`);
},
},
);
},
[orgSettings?.onboardingSettings, toast, updateTeamSettings],
);

const isSuggested = orgSettings?.onboardingSettings?.recommendedRepositories?.includes(configuration.id);

const inputId = useId({ prefix: "suggested-repository" });

return (
<ConfigurationSettingsField>
<Heading3 className="flex flex-row items-center gap-2">
Mark this repository as{" "}
<PillLabel className="capitalize bg-kumquat-light shrink-0 text-sm hidden xl:block" type="warn">
Suggested
</PillLabel>
</Heading3>
<Subheading className="max-w-lg flex flex-col gap-2">
The Suggested section highlights recommended repositories on the dashboard for new members, making it
easier to find and start working on key projects in Gitpod.
<a
className="gp-link flex flex-row items-center gap-1"
href="https://www.gitpod.io/docs/configure/orgs/onboarding#suggested-repositories"
target="_blank"
rel="noreferrer"
>
Learn about suggestions
<SquareArrowOutUpRight size={12} />
</a>
</Subheading>
<InputField id={inputId}>
<SwitchInputField
id={inputId}
checked={isSuggested}
disabled={updateTeamSettings.isLoading}
onCheckedChange={(checked) => {
updateRecommendedRepository(configuration.id, checked);
}}
label={isSuggested ? "Listed in “Suggested”" : "Not listed in “Suggested”"}
/>
</InputField>
</ConfigurationSettingsField>
);
};
50 changes: 3 additions & 47 deletions components/dashboard/src/repositories/list/RepoListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,15 @@ import { TextMuted } from "@podkit/typography/TextMuted";
import { Text } from "@podkit/typography/Text";
import { LinkButton } from "@podkit/buttons/LinkButton";
import type { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import { AlertTriangleIcon, CheckCircle2Icon, SquareArrowOutUpRight, Ellipsis } from "lucide-react";
import { AlertTriangleIcon, CheckCircle2Icon } from "lucide-react";
import { TableCell, TableRow } from "@podkit/tables/Table";
import { Button } from "@podkit/buttons/Button";
import {
DropdownLinkMenuItem,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@podkit/dropdown/DropDown";
import PillLabel from "../../components/PillLabel";

type Props = {
configuration: Configuration;
isSuggested: boolean;
handleModifySuggestedRepository?: (configurationId: string, suggested: boolean) => void;
};
export const RepositoryListItem: FC<Props> = ({ configuration, isSuggested, handleModifySuggestedRepository }) => {
export const RepositoryListItem: FC<Props> = ({ configuration, isSuggested }) => {
const url = usePrettyRepoURL(configuration.cloneUrl);
const prebuildsEnabled = !!configuration.prebuildSettings?.enabled;
const created =
Expand Down Expand Up @@ -73,45 +64,10 @@ export const RepositoryListItem: FC<Props> = ({ configuration, isSuggested, hand
</div>
</TableCell>

<TableCell className="flex items-center gap-4">
<TableCell>
<LinkButton href={`/repositories/${configuration.id}`} variant="secondary">
View
</LinkButton>
{handleModifySuggestedRepository && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<Ellipsis size={20} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52">
{isSuggested ? (
<DropdownMenuItem
onClick={() => handleModifySuggestedRepository(configuration.id, false)}
>
Remove from suggested repos
</DropdownMenuItem>
) : (
<>
<DropdownMenuItem
onClick={() => handleModifySuggestedRepository(configuration.id, true)}
>
Add to suggested repos
</DropdownMenuItem>
<DropdownLinkMenuItem
href="https://www.gitpod.io/docs/configure/orgs/onboarding#suggested-repositories"
className="gap-1 text-xs"
target="_blank"
rel="noreferrer"
>
Learn about suggestions
<SquareArrowOutUpRight size={12} />
</DropdownLinkMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
)}
</TableCell>
</TableRow>
);
Expand Down
27 changes: 0 additions & 27 deletions components/dashboard/src/repositories/list/RepositoryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import { SortableTableHead, TableSortOrder } from "@podkit/tables/SortableTable"
import { LoadingState } from "@podkit/loading/LoadingState";
import { Button } from "@podkit/buttons/Button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@podkit/select/Select";
import { useUpdateOrgSettingsMutation } from "../../data/organizations/update-org-settings-mutation";
import { useOrgSettingsQuery } from "../../data/organizations/org-settings-query";
import { useToast } from "../../components/toasts/Toasts";

type Props = {
configurations: Configuration[];
Expand Down Expand Up @@ -54,31 +52,7 @@ export const RepositoryTable: FC<Props> = ({
onLoadNextPage,
onSort,
}) => {
const updateTeamSettings = useUpdateOrgSettingsMutation();
const { data: settings } = useOrgSettingsQuery();
const { toast } = useToast();

const updateRecommendedRepository = async (configurationId: string, suggested: boolean) => {
const newRepositories = new Set(settings?.onboardingSettings?.recommendedRepositories ?? []);
if (suggested) {
newRepositories.add(configurationId);
} else {
newRepositories.delete(configurationId);
}

await updateTeamSettings.mutateAsync(
{
onboardingSettings: {
recommendedRepositories: [...newRepositories],
},
},
{
onError: (error) => {
toast(`Failed to update recommended repositories: ${error.message}`);
},
},
);
};

return (
<>
Expand Down Expand Up @@ -156,7 +130,6 @@ export const RepositoryTable: FC<Props> = ({
configuration.id,
) ?? false
}
handleModifySuggestedRepository={updateRecommendedRepository}
/>
);
})}
Expand Down
8 changes: 4 additions & 4 deletions components/dashboard/src/teams/TeamOnboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ export default function TeamOnboardingPage() {
<ConfigurationSettingsField>
<Heading3>Suggested repositories</Heading3>
<Subheading>
A list of repositories suggested to new organization members. To manage recommended
repositories, visit the{" "}
A list of repositories suggested to new organization members. You can toggle a repository's
visibility in the onboarding process by visiting the{" "}
<Link to="/repositories" className="gp-link">
Repository settings
</Link>{" "}
page and add / remove repositories from the list using the context menu on the corresponding
repository's row.
page and toggling the "Mark this repository as Suggested" setting under the details of the
repository.
</Subheading>
{(suggestedRepos ?? []).length > 0 && (
<Table className="mt-4">
Expand Down
Loading
Loading