-
Notifications
You must be signed in to change notification settings - Fork 610
feat: apis overview v2 #2918
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
feat: apis overview v2 #2918
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
36c32ac
feat: add new api cards
ogzhanolguncu 04f26a9
fix: granularity issues
ogzhanolguncu 2cc8a85
refactor: use same component for stat pages
ogzhanolguncu 9054d30
Merge branch 'main' of github.com:unkeyed/unkey into apis-overview-v2
ogzhanolguncu 6c3dd03
[autofix.ci] apply automated fixes
autofix-ci[bot] e54c387
feat: add pagiantion for overview cards
ogzhanolguncu 9f5467f
feat: add proper clean up for search overview
ogzhanolguncu 4886f27
fix: remove redundant fields
ogzhanolguncu a40c2d1
Merge branch 'apis-overview-v2' of github.com:unkeyed/unkey into apis…
ogzhanolguncu b1369d8
fix: dialog styles
ogzhanolguncu 4243009
fix: use sql syntax to prevent injection
ogzhanolguncu 3f1c95a
fix: use sql syntax instead of like
ogzhanolguncu e202155
Merge branch 'main' of github.com:unkeyed/unkey into apis-overview-v2
ogzhanolguncu 8a5c77e
fix: remove filters
ogzhanolguncu 7055195
chore: remove unused package
ogzhanolguncu 2257ba6
[autofix.ci] apply automated fixes
autofix-ci[bot] ac2ce02
fix: audit timestamp
ogzhanolguncu 848c315
Merge branch 'apis-overview-v2' of github.com:unkeyed/unkey into apis…
ogzhanolguncu bbddbf2
fix: pr comments
ogzhanolguncu 04b7667
Merge branch 'main' into apis-overview-v2
ogzhanolguncu 4a0f24b
Merge branch 'main' into apis-overview-v2
ogzhanolguncu 95c1c37
Merge branch 'main' of github.com:unkeyed/unkey into apis-overview-v2
ogzhanolguncu 1f6cb17
[autofix.ci] apply automated fixes
autofix-ci[bot] e7ac569
refactor: add auto submit to search component
ogzhanolguncu 746e742
Merge branch 'apis-overview-v2' of github.com:unkeyed/unkey into apis…
ogzhanolguncu e379ef7
feat: add new search component
ogzhanolguncu 88fbb72
refactor: use same same component to space out empty componetn
ogzhanolguncu 99530b7
Merge branch 'main' into apis-overview-v2
ogzhanolguncu cb1b915
fix: grid config
ogzhanolguncu c3c36ae
Merge branch 'apis-overview-v2' of github.com:unkeyed/unkey into apis…
ogzhanolguncu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
61 changes: 61 additions & 0 deletions
61
apps/dashboard/app/(app)/apis/_components/api-list-card.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| "use client"; | ||
| import { StatsCard } from "@/components/stats-card"; | ||
| import { StatsTimeseriesBarChart } from "@/components/stats-card/components/chart/stats-chart"; | ||
| import { MetricStats } from "@/components/stats-card/components/metric-stats"; | ||
| import type { ApiOverview } from "@/lib/trpc/routers/api/query-overview/schemas"; | ||
| import { Key, ProgressBar } from "@unkey/icons"; | ||
| import { useFetchVerificationTimeseries } from "./hooks/use-query-timeseries"; | ||
|
|
||
| type Props = { | ||
| api: ApiOverview; | ||
| }; | ||
|
|
||
| export const ApiListCard = ({ api }: Props) => { | ||
| const { timeseries, isLoading, isError } = useFetchVerificationTimeseries(api.keyspaceId); | ||
|
|
||
| const passed = timeseries?.reduce((acc, crr) => acc + crr.success, 0) ?? 0; | ||
| const blocked = timeseries?.reduce((acc, crr) => acc + crr.error, 0) ?? 0; | ||
|
|
||
| const keyCount = api.keys.reduce((acc, crr) => acc + crr.count, 0); | ||
| return ( | ||
| <StatsCard | ||
| name={api.name} | ||
| secondaryId={api.id} | ||
| linkPath={`/apis/${api.id}`} | ||
| chart={ | ||
| <StatsTimeseriesBarChart | ||
| data={timeseries} | ||
| isLoading={isLoading} | ||
| isError={isError} | ||
| config={{ | ||
| success: { | ||
| label: "Valid", | ||
| color: "hsl(var(--accent-4))", | ||
| }, | ||
| error: { | ||
| label: "Invalid", | ||
| color: "hsl(var(--orange-9))", | ||
| }, | ||
| }} | ||
| /> | ||
| } | ||
| stats={ | ||
| <> | ||
| <MetricStats | ||
| successCount={passed} | ||
| errorCount={blocked} | ||
| successLabel="VALID" | ||
| errorLabel="INVALID" | ||
| /> | ||
| <div className="flex items-center gap-2 min-w-0 max-w-[40%]"> | ||
| <Key className="text-accent-11 flex-shrink-0" /> | ||
| <div className="text-xs text-accent-9 truncate"> | ||
| {keyCount > 0 ? `${keyCount} ${keyCount === 1 ? "Key" : "Keys"}` : "No data"} | ||
| </div> | ||
| </div> | ||
| </> | ||
| } | ||
| icon={<ProgressBar className="text-accent-11" />} | ||
| /> | ||
| ); | ||
| }; |
84 changes: 84 additions & 0 deletions
84
apps/dashboard/app/(app)/apis/_components/api-list-client.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| "use client"; | ||
|
|
||
| import { EmptyComponentSpacer } from "@/components/empty-component-spacer"; | ||
| import type { | ||
| ApiOverview, | ||
| ApisOverviewResponse, | ||
| } from "@/lib/trpc/routers/api/query-overview/schemas"; | ||
| import { BookBookmark } from "@unkey/icons"; | ||
| import { Button, Empty } from "@unkey/ui"; | ||
| import { useState } from "react"; | ||
| import { ApiListGrid } from "./api-list-grid"; | ||
| import { ApiListControlCloud } from "./control-cloud"; | ||
| import { ApiListControls } from "./controls"; | ||
| import { CreateApiButton } from "./create-api-button"; | ||
|
|
||
| export const ApiListClient = ({ | ||
| initialData, | ||
| unpaid, | ||
| }: { | ||
| initialData: ApisOverviewResponse; | ||
| unpaid: boolean; | ||
| }) => { | ||
| const [isSearching, setIsSearching] = useState<boolean>(false); | ||
| const [apiList, setApiList] = useState<ApiOverview[]>(initialData.apiList); | ||
|
|
||
| if (unpaid) { | ||
| return ( | ||
| <EmptyComponentSpacer> | ||
| <Empty className="border border-gray-6 rounded-lg bg-gray-1"> | ||
| <Empty.Title className="text-xl">Upgrade your plan</Empty.Title> | ||
| <Empty.Description> | ||
| Team workspaces is a paid feature. Please switch to a paid plan to continue using it. | ||
| </Empty.Description> | ||
| <Empty.Actions className="mt-4 "> | ||
| <a href="/settings/billing" target="_blank" rel="noopener noreferrer"> | ||
| <Button> | ||
| <BookBookmark /> | ||
| Subscribe | ||
| </Button> | ||
| </a> | ||
| </Empty.Actions> | ||
| </Empty> | ||
| </EmptyComponentSpacer> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex flex-col"> | ||
| <ApiListControls apiList={apiList} onApiListChange={setApiList} onSearch={setIsSearching} /> | ||
| <ApiListControlCloud /> | ||
| {initialData.apiList.length > 0 ? ( | ||
| <ApiListGrid | ||
| isSearching={isSearching} | ||
| initialData={initialData} | ||
| setApiList={setApiList} | ||
| apiList={apiList} | ||
| /> | ||
| ) : ( | ||
| <EmptyComponentSpacer> | ||
| <Empty className="m-0 p-0"> | ||
| <Empty.Icon /> | ||
| <Empty.Title>No APIs found</Empty.Title> | ||
| <Empty.Description> | ||
| You haven't created any APIs yet. Create one to get started. | ||
| </Empty.Description> | ||
| <Empty.Actions className="mt-4 "> | ||
| <CreateApiButton /> | ||
| <a | ||
| href="https://www.unkey.com/docs/introduction" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| <Button> | ||
| <BookBookmark /> | ||
| Documentation | ||
| </Button> | ||
| </a> | ||
| </Empty.Actions> | ||
| </Empty> | ||
| </EmptyComponentSpacer> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; |
68 changes: 68 additions & 0 deletions
68
apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import { EmptyComponentSpacer } from "@/components/empty-component-spacer"; | ||
| import type { | ||
| ApiOverview, | ||
| ApisOverviewResponse, | ||
| } from "@/lib/trpc/routers/api/query-overview/schemas"; | ||
| import { ChevronDown } from "@unkey/icons"; | ||
| import { Button, Empty } from "@unkey/ui"; | ||
| import type { Dispatch, SetStateAction } from "react"; | ||
| import { ApiListCard } from "./api-list-card"; | ||
| import { useFetchApiOverview } from "./hooks/use-fetch-api-overview"; | ||
|
|
||
| export const ApiListGrid = ({ | ||
| initialData, | ||
| setApiList, | ||
| apiList, | ||
| isSearching, | ||
| }: { | ||
| initialData: ApisOverviewResponse; | ||
| apiList: ApiOverview[]; | ||
| setApiList: Dispatch<SetStateAction<ApiOverview[]>>; | ||
| isSearching?: boolean; | ||
| }) => { | ||
| const { total, loadMore, isLoading, hasMore } = useFetchApiOverview(initialData, setApiList); | ||
|
|
||
| if (apiList.length === 0) { | ||
| return ( | ||
| <EmptyComponentSpacer> | ||
| <Empty className="m-0 p-0"> | ||
| <Empty.Icon /> | ||
| <Empty.Title>No APIs found</Empty.Title> | ||
| <Empty.Description> | ||
| No APIs match your search criteria. Try a different search term. | ||
| </Empty.Description> | ||
| </Empty> | ||
| </EmptyComponentSpacer> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3 md:gap-5 w-full p-5"> | ||
| {apiList.map((api) => ( | ||
| <ApiListCard api={api} key={api.id} /> | ||
| ))} | ||
| </div> | ||
| <div className="flex flex-col items-center justify-center mt-8 space-y-4"> | ||
| <div className="text-center text-sm text-accent-11"> | ||
| Showing {apiList.length} of {total} APIs | ||
| </div> | ||
| {!isSearching && hasMore && ( | ||
| <Button onClick={loadMore} disabled={isLoading}> | ||
| {isLoading ? ( | ||
| <div className="flex items-center space-x-2"> | ||
| <div className="animate-spin h-4 w-4 border-2 border-gray-7 border-t-transparent rounded-full" /> | ||
| <span>Loading...</span> | ||
| </div> | ||
| ) : ( | ||
| <div className="flex items-center space-x-2"> | ||
| <ChevronDown /> | ||
| <span>Load more</span> | ||
| </div> | ||
| )} | ||
| </Button> | ||
| )} | ||
| </div> | ||
| </> | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export const DEFAULT_OVERVIEW_FETCH_LIMIT = 9; |
29 changes: 29 additions & 0 deletions
29
apps/dashboard/app/(app)/apis/_components/control-cloud/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { HISTORICAL_DATA_WINDOW } from "@/components/logs/constants"; | ||
| import { ControlCloud } from "@/components/logs/control-cloud"; | ||
| import { useFilters } from "../hooks/use-filters"; | ||
|
|
||
| const formatFieldName = (field: string): string => { | ||
| switch (field) { | ||
| case "startTime": | ||
| return "Start time"; | ||
| case "endTime": | ||
| return "End time"; | ||
| case "since": | ||
| return ""; | ||
| default: | ||
| return field.charAt(0).toUpperCase() + field.slice(1); | ||
| } | ||
| }; | ||
|
|
||
| export const ApiListControlCloud = () => { | ||
| const { filters, updateFilters, removeFilter } = useFilters(); | ||
| return ( | ||
| <ControlCloud | ||
| historicalWindow={HISTORICAL_DATA_WINDOW} | ||
| formatFieldName={formatFieldName} | ||
| filters={filters} | ||
| removeFilter={removeFilter} | ||
| updateFilters={updateFilters} | ||
| /> | ||
| ); | ||
| }; |
88 changes: 88 additions & 0 deletions
88
apps/dashboard/app/(app)/apis/_components/controls/components/logs-datetime/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { DatetimePopover } from "@/components/logs/datetime/datetime-popover"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { Calendar } from "@unkey/icons"; | ||
| import { Button } from "@unkey/ui"; | ||
| import { useEffect, useState } from "react"; | ||
| import { useFilters } from "../../../hooks/use-filters"; | ||
|
|
||
| export const LogsDateTime = () => { | ||
| const [title, setTitle] = useState<string | null>(null); | ||
| const { filters, updateFilters } = useFilters(); | ||
|
|
||
| useEffect(() => { | ||
| if (!title) { | ||
| setTitle("Last 12 hours"); | ||
| } | ||
| }, [title]); | ||
|
|
||
| const timeValues = filters | ||
| .filter((f) => ["startTime", "endTime", "since"].includes(f.field)) | ||
| .reduce( | ||
| (acc, f) => ({ | ||
| // biome-ignore lint/performance/noAccumulatingSpread: it's safe to spread | ||
| ...acc, | ||
| [f.field]: f.value, | ||
| }), | ||
| {}, | ||
| ); | ||
|
|
||
| return ( | ||
| <DatetimePopover | ||
| initialTimeValues={timeValues} | ||
| onDateTimeChange={(startTime, endTime, since) => { | ||
| const activeFilters = filters.filter( | ||
| (f) => !["endTime", "startTime", "since"].includes(f.field), | ||
| ); | ||
| if (since !== undefined) { | ||
| updateFilters([ | ||
| ...activeFilters, | ||
| { | ||
| field: "since", | ||
| value: since, | ||
| id: crypto.randomUUID(), | ||
| operator: "is", | ||
| }, | ||
| ]); | ||
| return; | ||
| } | ||
| if (since === undefined && startTime) { | ||
| activeFilters.push({ | ||
| field: "startTime", | ||
| value: startTime, | ||
| id: crypto.randomUUID(), | ||
| operator: "is", | ||
| }); | ||
| if (endTime) { | ||
| activeFilters.push({ | ||
| field: "endTime", | ||
| value: endTime, | ||
| id: crypto.randomUUID(), | ||
| operator: "is", | ||
| }); | ||
| } | ||
| } | ||
| updateFilters(activeFilters); | ||
| }} | ||
| initialTitle={title ?? ""} | ||
| onSuggestionChange={setTitle} | ||
| > | ||
| <div className="group"> | ||
| <Button | ||
| variant="ghost" | ||
| className={cn( | ||
| "group-data-[state=open]:bg-gray-4 px-2", | ||
| !title ? "opacity-50" : "", | ||
| title !== "Last 12 hours" ? "bg-gray-4" : "", | ||
| )} | ||
| aria-label="Filter logs by time" | ||
| aria-haspopup="true" | ||
| title="Press 'T' to toggle filters" | ||
| disabled={!title} | ||
| > | ||
| <Calendar className="text-gray-9 size-4" /> | ||
| <span className="text-gray-12 font-medium text-[13px]">{title ?? "Loading..."}</span> | ||
| </Button> | ||
| </div> | ||
| </DatetimePopover> | ||
| ); | ||
| }; |
18 changes: 18 additions & 0 deletions
18
apps/dashboard/app/(app)/apis/_components/controls/components/logs-refresh.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { RefreshButton } from "@/components/logs/refresh-button"; | ||
| import { trpc } from "@/lib/trpc/client"; | ||
| import { useRouter } from "next/navigation"; | ||
| import { useFilters } from "../../hooks/use-filters"; | ||
|
|
||
| export const LogsRefresh = () => { | ||
| const { filters } = useFilters(); | ||
| const { api } = trpc.useUtils(); | ||
| const { refresh } = useRouter(); | ||
| const hasRelativeFilter = filters.find((f) => f.field === "since"); | ||
|
|
||
| const handleRefresh = () => { | ||
| api.logs.queryVerificationTimeseries.invalidate(); | ||
| refresh(); | ||
| }; | ||
|
|
||
| return <RefreshButton onRefresh={handleRefresh} isEnabled={Boolean(hasRelativeFilter)} />; | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.