Skip to content

Configuration Detail setup #19021

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 55 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
931fa09
Configuration Detail setup
filiptronicek Nov 6, 2023
cd927ad
Merge branch 'main' into ft/config-overview
filiptronicek Nov 6, 2023
99dc62d
Merge branch 'main' into ft/config-overview
filiptronicek Nov 7, 2023
bb15531
nav
filiptronicek Nov 7, 2023
7e5cf90
Breadcrumb above nav
filiptronicek Nov 8, 2023
571c570
Merge branch 'main' into ft/config-overview
filiptronicek Nov 8, 2023
e6c0a28
type updates
filiptronicek Nov 8, 2023
277d949
Useconfig
filiptronicek Nov 8, 2023
d873f36
Fix undefined vs null
filiptronicek Nov 8, 2023
e7940c1
Fix children rendering
filiptronicek Nov 8, 2023
96a3ba2
Update colors
filiptronicek Nov 8, 2023
befffa8
Cancel buttons
filiptronicek Nov 8, 2023
174c358
Delete configs
filiptronicek Nov 9, 2023
2af5ab5
Merge branch 'main' into ft/config-overview
filiptronicek Nov 9, 2023
b6b3de7
Refactor configuration detail page to use new
filiptronicek Nov 9, 2023
5b2ab65
Fix styling and layout issues in BreadcrumbNav and
filiptronicek Nov 9, 2023
a65b333
Update BreadcrumbNav and ConfigurationDetailPage
filiptronicek Nov 9, 2023
98d5053
Fix spacings
filiptronicek Nov 9, 2023
5989376
Update BreadcrumbNav styling
filiptronicek Nov 9, 2023
248e995
Containerify
filiptronicek Nov 9, 2023
aac77b3
Update configuration name and style, and add dark
filiptronicek Nov 9, 2023
a48f1b8
Update BreadcrumbNav styling to include dark mode
filiptronicek Nov 9, 2023
6094f50
Fix text color in BreadcrumbNav component
filiptronicek Nov 9, 2023
b11b7f6
Add WidePageWithSubMenu component and export
filiptronicek Nov 10, 2023
209e106
Revert `PageWithSubMenu` changes
filiptronicek Nov 10, 2023
b5450c2
Fix types
filiptronicek Nov 10, 2023
ebb1652
Unify margins for ConfigurationName
filiptronicek Nov 10, 2023
6acd1f0
Icons
filiptronicek Nov 10, 2023
5d6151b
Merge branch 'ft/config-overview' of https://github.com/gitpod-io/git…
filiptronicek Nov 10, 2023
4c921cf
Refactor configuration deletion queries
filiptronicek Nov 10, 2023
053612c
Less useStates :}
filiptronicek Nov 10, 2023
fd22195
Call `onRemoved` only at `onSuccess`
filiptronicek Nov 11, 2023
a036a30
Add MiddleDot component to BreadcrumbNav
filiptronicek Nov 11, 2023
dbd30f7
Fix rectangular focus indicators
filiptronicek Nov 11, 2023
711274c
Add icons to configurations menu
filiptronicek Nov 11, 2023
e8d3b3f
navs
filiptronicek Nov 11, 2023
077e0fe
Conditionally render children
filiptronicek Nov 11, 2023
f96c097
Fix rectangular list items
filiptronicek Nov 11, 2023
fc62dfe
`configurations` -> `repositories` underneath 🫣
filiptronicek Nov 11, 2023
7e91bab
Update links in OrganizationSelector and
filiptronicek Nov 11, 2023
03143d4
Update BreadcrumbNav to use larger font size for
filiptronicek Nov 11, 2023
163949c
Componentize config setting fields
filiptronicek Nov 11, 2023
03c2213
Merge branch 'main' into ft/config-overview
filiptronicek Nov 13, 2023
d5f267d
y margin
filiptronicek Nov 13, 2023
9b2c06f
Fix contrast for Confirmation modal descriptions
filiptronicek Nov 13, 2023
82a2f56
use dirty state hook and fix cancelling after updating
filiptronicek Nov 13, 2023
e5b5851
Re-structure
filiptronicek Nov 13, 2023
99b8e26
`asChild` for podkit headings
filiptronicek Nov 13, 2023
f1c0b73
Fix save button disabled state in
filiptronicek Nov 13, 2023
5a4a5bc
Add type attribute to button in
filiptronicek Nov 13, 2023
5c7a74e
Remove hack, which didn't work 🙃
filiptronicek Nov 13, 2023
908ea12
Fix error handling in ConfigurationDetailPage
filiptronicek Nov 13, 2023
e0c7dcd
Refactor sub-menu item styles to improve
filiptronicek Nov 13, 2023
1d4a7f1
Update ConfigurationNameForm component with
filiptronicek Nov 13, 2023
64c2170
Do not memoize 🤖
filiptronicek Nov 14, 2023
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
12 changes: 7 additions & 5 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ const WorkspacesSearch = React.lazy(() => import(/* webpackPrefetch: true */ "..
const ProjectsSearch = React.lazy(() => import(/* webpackPrefetch: true */ "../admin/ProjectsSearch"));
const TeamsSearch = React.lazy(() => import(/* webpackPrefetch: true */ "../admin/TeamsSearch"));
const Usage = React.lazy(() => import(/* webpackPrefetch: true */ "../Usage"));
const RepositoryListPage = React.lazy(() => import(/* webpackPrefetch: true */ "../repositories/list/RepositoryList"));
const RepositoryDetailPage = React.lazy(
() => import(/* webpackPrefetch: true */ "../repositories/detail/RepositoryDetail"),
const ConfigurationListPage = React.lazy(
() => import(/* webpackPrefetch: true */ "../repositories/list/RepositoryList"),
);
const ConfigurationGeneral = React.lazy(
() => import(/* webpackPrefetch: true */ "../repositories/detail/ConfigurationDetailGeneral"),
);

export const AppRoutes = () => {
Expand Down Expand Up @@ -218,8 +220,8 @@ export const AppRoutes = () => {

{repoConfigListAndDetail && (
<>
<Route exact path="/repositories" component={RepositoryListPage} />
<Route exact path="/repositories/:id" component={RepositoryDetailPage} />
<Route exact path="/repositories" component={ConfigurationListPage} />
<Route exact path="/repositories/:id" component={ConfigurationGeneral} />
</>
)}
{/* basic redirect for old team slugs */}
Expand Down
4 changes: 3 additions & 1 deletion components/dashboard/src/components/ConfirmationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ export const ConfirmationModal: FC<Props> = ({
{isEntity(children) ? (
<div className="w-full p-4 mb-2 bg-gray-100 dark:bg-gray-700 rounded-xl group">
<p className="text-base text-gray-800 dark:text-gray-100 font-semibold">{children.name}</p>
{children.description && <p className="text-gray-500 truncate">{children.description}</p>}
{children.description && (
<p className="text-gray-500 dark:text-gray-300 truncate">{children.description}</p>
)}
</div>
) : (
children
Expand Down
22 changes: 11 additions & 11 deletions components/dashboard/src/components/PageWithSubMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import { Separator } from "./Separator";
export interface PageWithSubMenuProps {
title: string;
subtitle: string;
subMenu: {
title: string;
link: string[];
}[];
subMenu: SubmenuItemProps[];
tabs?: TabEntry[];
children: React.ReactNode;
}
Expand Down Expand Up @@ -53,12 +50,13 @@ export function PageWithSubMenu(p: PageWithSubMenuProps) {
);
}

type SubmenuItemProps = {
export type SubmenuItemProps = {
title: string;
link: string[];
icon?: React.ReactNode;
};

const SubmenuItem: FC<SubmenuItemProps> = ({ title, link }) => {
export const SubmenuItem: FC<SubmenuItemProps> = ({ title, link, icon }) => {
const location = useLocation();
const itemRef = useRef<HTMLLIElement>(null);

Expand All @@ -69,17 +67,19 @@ const SubmenuItem: FC<SubmenuItemProps> = ({ title, link }) => {
}
}, [link, location.pathname]);

let classes = "flex block py-2 px-4 rounded-md whitespace-nowrap max-w-52";
let classes = "flex justify-between block rounded-md py-2 px-4 whitespace-nowrap max-w-52";

if (link.some((l) => l === location.pathname)) {
const isCurrent = link.some((l) => l === location.pathname);
if (isCurrent) {
classes += " bg-gray-300 text-gray-800 dark:bg-gray-800 dark:text-gray-50";
} else {
classes += " hover:bg-gray-100 dark:hover:bg-gray-800";
classes += " hover:bg-gray-100 dark:hover:bg-gray-800 dark:text-gray-400";
}

return (
<Link to={link[0]} key={title} className="md:w-full">
<Link to={link[0]} key={title} className="md:w-full rounded-md">
<li ref={itemRef} className={classes}>
{title}
{title} {icon}
</li>
</Link>
);
Expand Down
49 changes: 49 additions & 0 deletions components/dashboard/src/components/WidePageWithSubmenu.tsx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not necessarily love that I had to introduce yet another component which is oh so similar to the PageWithSubmenu, but I did not want to make invasive changes to the component we use in many other places in the Dashboard, and at the same time, keep up with the current designs. Tried to re-use code when possible here to reduce the footprint.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2023 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 classNames from "classnames";
import { Separator } from "./Separator";
import { cn } from "@podkit/lib/cn";
import { SubmenuItem, SubmenuItemProps } from "./PageWithSubMenu";

export interface PageWithSubMenuProps {
/**
* The name of the navigation menu, as read by screen readers.
*/
navTitle?: string;
subMenu: SubmenuItemProps[];
children: React.ReactNode;
}

export function WidePageWithSubMenu(p: PageWithSubMenuProps) {
return (
<div className="w-full">
<div className={cn("app-container flex flex-col md:flex-row")}>
{/* TODO: extract into SubMenu component and show scrolling indicators */}
<nav aria-label={p.navTitle}>
<ul
className={classNames(
// Handle flipping between row and column layout
"flex flex-row md:flex-col items-center",
"w-full md:w-52 overflow-auto md:overflow-visible",
"pt-4 pb-4 md:pb-0",
"space-x-2 md:space-x-0 md:space-y-2",
"tracking-wide text-gray-500",
)}
>
{p.subMenu.map((e) => {
return <SubmenuItem title={e.title} link={e.link} key={e.title} icon={e.icon} />;
})}
</ul>
</nav>
<div className="md:ml-4 w-full pt-1 mb-40">
<Separator className="md:hidden" />
<div className="pt-4 md:pt-0">{p.children}</div>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2023 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 { LinkButton } from "@podkit/buttons/LinkButton";
import { cn } from "@podkit/lib/cn";
import { ChevronLeft } from "lucide-react";
import type { FC } from "react";
import { MiddleDot } from "../../typography/MiddleDot";
import { Heading3 } from "@podkit/typography/Headings";

interface BreadcrumbPageNavProps {
/**
* The title of the current page.
*/
pageTitle: string;
/**
* The description of the current page.
*/
pageDescription?: string;
/**
* The link to the previous page.
*/
backLink?: string;
className?: string;
}

export const BreadcrumbNav: FC<BreadcrumbPageNavProps> = ({ pageTitle, pageDescription, backLink, className }) => {
return (
<section className={cn("flex flex-row items-center justify-start gap-2 w-full py-4 app-container", className)}>
{backLink && (
<LinkButton
variant={"ghost"}
className="py-1 px-0 hover:bg-gray-200 dark:hover:bg-gray-800 dark:hover:text-gray-200"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We ought to have a more bare-bones ghost variant, or introduce the icon variant for easy Icon buttons.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixes to ghosts are actually done in #19039. Nice!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe once we've completed this suite of config UIs we can kind of recap buttons and how we used them.

href={backLink}
>
<ChevronLeft size={24} />
</LinkButton>
)}
<Heading3 asChild>
<h1>{pageTitle}</h1>
</Heading3>
<MiddleDot />
<p className="text-gray-900 dark:text-gray-300 text-lg">{pageDescription}</p>
</section>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,49 @@

import { cn } from "@podkit/lib/cn";
import { FC } from "react";
import { Slot } from "@radix-ui/react-slot";

type HeadingProps = {
id?: string;
tracking?: "tight" | "wide";
color?: "light" | "dark";
className?: string;
asChild?: boolean;
};

export const Heading1: FC<HeadingProps> = ({ id, color, tracking, className, children }) => {
export const Heading1: FC<HeadingProps> = ({ id, color, tracking, className, children, asChild }) => {
const Comp = asChild ? Slot : "h1";

return (
<h1
<Comp
id={id}
className={cn(getHeadingColor(color), getTracking(tracking), "font-bold text-4xl truncate", className)}
>
{children}
</h1>
</Comp>
);
};

export const Heading2: FC<HeadingProps> = ({ id, color, tracking, className, children }) => {
export const Heading2: FC<HeadingProps> = ({ id, color, tracking, className, children, asChild }) => {
const Comp = asChild ? Slot : "h2";

return (
<h2 id={id} className={cn(getHeadingColor(color), getTracking(tracking), "font-semibold text-2xl", className)}>
<Comp
id={id}
className={cn(getHeadingColor(color), getTracking(tracking), "font-semibold text-2xl", className)}
>
{children}
</h2>
</Comp>
);
};

export const Heading3: FC<HeadingProps> = ({ id, color, tracking, className, children }) => {
export const Heading3: FC<HeadingProps> = ({ id, color, tracking, className, children, asChild }) => {
const Comp = asChild ? Slot : "h3";

return (
<h3 id={id} className={cn(getHeadingColor(color), getTracking(tracking), "font-semibold text-lg", className)}>
<Comp id={id} className={cn(getHeadingColor(color), getTracking(tracking), "font-semibold text-lg", className)}>
{children}
</h3>
</Comp>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* See License.AGPL.txt in the project root for license information.
*/

import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useCurrentOrg } from "../organizations/orgs-query";
import { configurationClient } from "../../service/public-api";
import { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import type { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";

const BASE_KEY = "configurations";

Expand Down Expand Up @@ -60,6 +60,25 @@ export const useConfiguration = (configurationId: string) => {
});
};

type DeleteConfigurationArgs = {
configurationId: string;
};
export const useDeleteConfiguration = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async ({ configurationId }: DeleteConfigurationArgs) => {
return await configurationClient.deleteConfiguration({
configurationId,
});
},
onSuccess: (_, { configurationId }) => {
queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });
queryClient.invalidateQueries({ queryKey: getConfigurationQueryKey(configurationId) });
},
});
};

export const getConfigurationQueryKey = (configurationId: string) => {
const key: any[] = [BASE_KEY, { configurationId }];

Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/menu/OrganizationSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export default function OrganizationSelector() {
if (currentOrg.data) {
if (repoConfigListAndDetail) {
linkEntries.push({
title: "Repositories",
customContent: <LinkEntry>Repositories</LinkEntry>,
title: "Configurations",
customContent: <LinkEntry>Configurations</LinkEntry>,
active: false,
separator: false,
link: "/repositories",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2023 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 { FC } from "react";
import { useParams } from "react-router";
import { ConfigurationNameForm } from "./general/ConfigurationName";
import { ConfigurationDetailPage } from "./ConfigurationDetailPage";
import { useConfiguration } from "../../data/configurations/configuration-queries";
import { RemoveConfiguration } from "./general/RemoveConfiguration";

type PageRouteParams = {
id: string;
};
const ConfigurationDetailGeneral: FC = () => {
const { id } = useParams<PageRouteParams>();
const configurationQuery = useConfiguration(id);
const { data } = configurationQuery;

return (
<ConfigurationDetailPage configurationQuery={configurationQuery} id={id}>
{data && (
<>
<ConfigurationNameForm configuration={data} />
<RemoveConfiguration configuration={data} />
</>
)}
</ConfigurationDetailPage>
);
};

export default ConfigurationDetailGeneral;
Loading