Skip to content

Commit

Permalink
Adding prebuild status filter to repo configs list ui (#19205)
Browse files Browse the repository at this point in the history
* Adding prebuildsEnabled filter to projects search query

* adding prebuilds_enabled param

* stubbing out UI for prebuild status filter

* mark prebuilds_enabled as optional so we have an undefined state

* ui stuff

* whoopsie

* drop animations for now

* comments

* cleanup

* convert to string

* consider prebuild filter in showing table

* add prebuilds query param

* fix issue w/ fragment around routes preventing 404 handler
  • Loading branch information
selfcontained authored Dec 8, 2023
1 parent 277c9da commit 85fb744
Show file tree
Hide file tree
Showing 15 changed files with 470 additions and 138 deletions.
1 change: 1 addition & 0 deletions components/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@stripe/react-stripe-js": "^1.7.2",
Expand Down
13 changes: 4 additions & 9 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,9 @@ export const AppRoutes = () => {
<Route exact path={`/projects/:projectSlug/variables`} component={ProjectVariables} />
<Route exact path={`/projects/:projectSlug/:prebuildId`} component={Prebuild} />

{repoConfigListAndDetail && (
<>
<Route exact path="/repositories" component={ConfigurationListPage} />
{/* Handles all /repositories/:id/* routes in a nested router */}
<Route path="/repositories/:id" component={ConfigurationDetailPage} />
</>
)}
{repoConfigListAndDetail && <Route exact path="/repositories" component={ConfigurationListPage} />}
{/* Handles all /repositories/:id/* routes in a nested router */}
{repoConfigListAndDetail && <Route path="/repositories/:id" component={ConfigurationDetailPage} />}
{/* basic redirect for old team slugs */}
<Route path={["/t/"]} exact>
<Redirect to="/projects" />
Expand Down Expand Up @@ -251,7 +247,6 @@ export const AppRoutes = () => {
// delegate to our website to handle the request
if (isGitpodIo()) {
window.location.host = "www.gitpod.io";
return;
}

return (
Expand All @@ -261,7 +256,7 @@ export const AppRoutes = () => {
</div>
);
}}
></Route>
/>
</Switch>
</div>
<WebsocketClients />
Expand Down
161 changes: 161 additions & 0 deletions components/dashboard/src/components/podkit/select/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* 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 * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@podkit/lib/cn";

const Select = SelectPrimitive.Root;

const SelectGroup = SelectPrimitive.Group;

const SelectValue = SelectPrimitive.Value;

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex items-center justify-between w-full max-w-lg",
"rounded-lg py-[7px] px-2",
"text-sm text-pk-content-primary placeholder:text-pk-content-tertiary",
"bg-pk-surface-primary",
"border border-pk-border-base",
"text-sm",
"hover:[&_svg]:text-pk-content-primary",
"focus:outline-none focus-visible:ring-4 focus-visible:ring-ring",
"disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 text-pk-content-disabled" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;

const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;

const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;

const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden",
"rounded-lg border border-pk-border-base",
"bg-pk-surface-primary text-pk-content-primary shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;

const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label ref={ref} className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} {...props} />
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;

const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default items-center select-none",
"rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
"focus:bg-pk-surface-tertiary focus:text-accent-foreground",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>

<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;

const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-pk-border-base", className)} {...props} />
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;

export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ const BASE_KEY = "configurations";
type ListConfigurationsArgs = {
pageSize?: number;
searchTerm?: string;
prebuildsEnabled?: boolean;
sortBy: string;
sortOrder: TableSortOrder;
};

export const useListConfigurations = ({ searchTerm = "", pageSize, sortBy, sortOrder }: ListConfigurationsArgs) => {
export const useListConfigurations = (options: ListConfigurationsArgs) => {
const { data: org } = useCurrentOrg();
const { searchTerm = "", prebuildsEnabled, pageSize, sortBy, sortOrder } = options;

return useInfiniteQuery(
getListConfigurationsQueryKey(org?.id || "", { searchTerm, pageSize, sortBy, sortOrder }),
getListConfigurationsQueryKey(org?.id || "", options),
// QueryFn receives the past page's pageParam as it's argument
async ({ pageParam: nextToken }) => {
if (!org) {
Expand All @@ -40,6 +42,7 @@ export const useListConfigurations = ({ searchTerm = "", pageSize, sortBy, sortO
const { configurations, pagination } = await configurationClient.listConfigurations({
organizationId: org.id,
searchTerm,
prebuildsEnabled,
pagination: { pageSize, token: nextToken },
sort: [
{
Expand Down
23 changes: 21 additions & 2 deletions components/dashboard/src/repositories/list/RepositoryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import { RepositoryTable } from "./RepositoryTable";
import { LoadingState } from "@podkit/loading/LoadingState";
import { TableSortOrder } from "@podkit/tables/SortableTable";

const PREBUILD_FILTERS = { all: undefined, enabled: true, disabled: false };

const RepositoryListPage: FC = () => {
useDocumentTitle("Imported repositories");

const history = useHistory();

const params = useQueryParams();
const [searchTerm, setSearchTerm, searchTermDebounced] = useStateWithDebounce(params.get("search") || "");
const [prebuildsFilter, setPrebuildsFilter] = useState(parsePrebuilds(params));
const [sortBy, setSortBy] = useState(parseSortBy(params));
const [sortOrder, setSortOrder] = useState<TableSortOrder>(parseSortOrder(params));
const [showCreateProjectModal, setShowCreateProjectModal] = useState(false);
Expand All @@ -42,16 +45,22 @@ const RepositoryListPage: FC = () => {
if (sortOrder) {
params.set("sortOrder", sortOrder);
}
// Since "all" is the default, we don't need to set it in the url
if (prebuildsFilter !== "all") {
params.set("prebuilds", prebuildsFilter);
}
params.toString();
history.replace({ search: `?${params.toString()}` });
}, [history, searchTermDebounced, sortBy, sortOrder]);
}, [history, prebuildsFilter, searchTermDebounced, sortBy, sortOrder]);

// TODO: handle isError case
const { data, isLoading, isFetching, isFetchingNextPage, isPreviousData, hasNextPage, fetchNextPage } =
useListConfigurations({
searchTerm: searchTermDebounced,
sortBy: sortBy,
sortOrder: sortOrder,
// Map ui prebuildFilter state to the right api value
prebuildsEnabled: { all: undefined, enabled: true, disabled: false }[prebuildsFilter],
});

const handleRepoImported = useCallback(
Expand All @@ -76,7 +85,7 @@ const RepositoryListPage: FC = () => {
const hasMoreThanOnePage = (data?.pages.length ?? 0) > 1;

// This tracks any filters/search params applied
const hasFilters = !!searchTermDebounced;
const hasFilters = !!searchTermDebounced || prebuildsFilter !== "all";

// Show the table once we're done loading and either have results, or have filters applied
const showTable = !isLoading && (configurations.length > 0 || hasFilters);
Expand All @@ -94,6 +103,7 @@ const RepositoryListPage: FC = () => {
{showTable && (
<RepositoryTable
searchTerm={searchTerm}
prebuildsFilter={prebuildsFilter}
configurations={configurations}
sortBy={sortBy}
sortOrder={sortOrder}
Expand All @@ -105,6 +115,7 @@ const RepositoryListPage: FC = () => {
onImport={() => setShowCreateProjectModal(true)}
onLoadNextPage={() => fetchNextPage()}
onSearchTermChange={setSearchTerm}
onPrebuildsFilterChange={setPrebuildsFilter}
onSort={handleSort}
/>
)}
Expand Down Expand Up @@ -139,3 +150,11 @@ const parseSortBy = (params: URLSearchParams) => {
}
return "name";
};

const parsePrebuilds = (params: URLSearchParams): keyof typeof PREBUILD_FILTERS => {
const prebuilds = params.get("prebuilds");
if (prebuilds === "enabled" || prebuilds === "disabled") {
return prebuilds;
}
return "all";
};
18 changes: 16 additions & 2 deletions components/dashboard/src/repositories/list/RepositoryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import { cn } from "@podkit/lib/cn";
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";

type Props = {
configurations: Configuration[];
searchTerm: string;
prebuildsFilter: string;
sortBy: string;
sortOrder: "asc" | "desc";
hasNextPage: boolean;
Expand All @@ -28,12 +30,14 @@ type Props = {
isFetchingNextPage: boolean;
onImport: () => void;
onSearchTermChange: (val: string) => void;
onPrebuildsFilterChange: (val: "all" | "enabled" | "disabled") => void;
onLoadNextPage: () => void;
onSort: (columnName: string, direction: TableSortOrder) => void;
};

export const RepositoryTable: FC<Props> = ({
searchTerm,
prebuildsFilter,
configurations,
sortOrder,
sortBy,
Expand All @@ -43,22 +47,32 @@ export const RepositoryTable: FC<Props> = ({
isFetchingNextPage,
onImport,
onSearchTermChange,
onPrebuildsFilterChange,
onLoadNextPage,
onSort,
}) => {
return (
<>
{/* Search/Filter bar */}
<div className="flex flex-col-reverse md:flex-row flex-wrap justify-between items-center gap-2">
<div className="flex flex-row flex-wrap items-center w-full md:w-auto">
<div className="flex flex-row flex-wrap gap-2 items-center w-full md:w-auto">
{/* TODO: Add search icon on left - need to revisit TextInputs for podkit - and remove global styles */}
<TextInput
className="w-full max-w-none md:w-80"
value={searchTerm}
onChange={onSearchTermChange}
placeholder="Search imported repositories"
/>
{/* TODO: Add prebuild status filter dropdown */}
<Select value={prebuildsFilter} onValueChange={onPrebuildsFilterChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Prebuilds: All" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Prebuilds: All</SelectItem>
<SelectItem value="enabled">Prebuilds: Enabled</SelectItem>
<SelectItem value="disabled">Prebuilds: Disabled</SelectItem>
</SelectContent>
</Select>
</div>

{/* TODO: Consider making all podkit buttons behave this way, full width on small screen */}
Expand Down
Loading

0 comments on commit 85fb744

Please sign in to comment.