diff --git a/internal/database/release.go b/internal/database/release.go index 9379b85..071d873 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -551,7 +551,8 @@ FROM ( CROSS JOIN ( SELECT COUNT(CASE WHEN status = 'PUSH_APPROVED' THEN 0 END) AS push_approved_count, - COUNT(CASE WHEN status = 'PUSH_REJECTED' THEN 0 END) AS push_rejected_count + COUNT(CASE WHEN status = 'PUSH_REJECTED' THEN 0 END) AS push_rejected_count, + COUNT(CASE WHEN status = 'PUSH_ERROR' THEN 0 END) AS push_error_count FROM release_action_status ) AS foo` @@ -562,7 +563,7 @@ CROSS JOIN ( var rls domain.ReleaseStats - if err := row.Scan(&rls.TotalCount, &rls.FilteredCount, &rls.FilterRejectedCount, &rls.PushApprovedCount, &rls.PushRejectedCount); err != nil { + if err := row.Scan(&rls.TotalCount, &rls.FilteredCount, &rls.FilterRejectedCount, &rls.PushApprovedCount, &rls.PushRejectedCount, &rls.PushErrorCount); err != nil { return nil, errors.Wrap(err, "error scanning row") } diff --git a/internal/domain/release.go b/internal/domain/release.go index 2275473..c82d312 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -150,6 +150,7 @@ type ReleaseStats struct { FilterRejectedCount int64 `json:"filter_rejected_count"` PushApprovedCount int64 `json:"push_approved_count"` PushRejectedCount int64 `json:"push_rejected_count"` + PushErrorCount int64 `json:"push_error_count"` } type ReleasePushStatus string diff --git a/web/src/components/data-table/Cells.tsx b/web/src/components/data-table/Cells.tsx index e58a6f6..531b7f0 100644 --- a/web/src/components/data-table/Cells.tsx +++ b/web/src/components/data-table/Cells.tsx @@ -8,6 +8,8 @@ import { toast } from "react-hot-toast"; import { formatDistanceToNowStrict } from "date-fns"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid"; +import { ArrowDownTrayIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; +import { ExternalLink } from "../ExternalLink"; import { ClockIcon, XMarkIcon, NoSymbolIcon } from "@heroicons/react/24/outline"; import { APIClient } from "@api/APIClient"; @@ -21,6 +23,10 @@ interface CellProps { value: string; } +interface LinksCellProps { + value: Release; +} + export const AgeCell = ({ value }: CellProps) => (
{formatDistanceToNowStrict(new Date(value), { addSuffix: false })} @@ -243,3 +249,20 @@ export const ReleaseStatusCell = ({ value }: ReleaseStatusCellProps) => ( ))}
); + +export const LinksCell = ({ value }: LinksCellProps) => { + return ( +
+ {value.download_url && ( + + + )} + {value.info_url && ( + + + )} +
+ ); +}; diff --git a/web/src/components/data-table/index.tsx b/web/src/components/data-table/index.tsx index 14fe1c4..7d52dd9 100644 --- a/web/src/components/data-table/index.tsx +++ b/web/src/components/data-table/index.tsx @@ -4,4 +4,4 @@ */ export { Button, PageButton } from "./Buttons"; -export { AgeCell, IndexerCell, TitleCell, ReleaseStatusCell } from "./Cells"; \ No newline at end of file +export { AgeCell, IndexerCell, TitleCell, ReleaseStatusCell, LinksCell } from "./Cells"; diff --git a/web/src/screens/dashboard/Stats.tsx b/web/src/screens/dashboard/Stats.tsx index a458840..2ac10ec 100644 --- a/web/src/screens/dashboard/Stats.tsx +++ b/web/src/screens/dashboard/Stats.tsx @@ -6,33 +6,49 @@ import { useQuery } from "@tanstack/react-query"; import { APIClient } from "@api/APIClient"; import { classNames } from "@utils"; +import { useNavigate } from "react-router-dom"; +import { LinkIcon } from "@heroicons/react/24/solid"; interface StatsItemProps { name: string; value?: number; placeholder?: string; + onClick?: () => void; } -const StatsItem = ({ name, placeholder, value }: StatsItemProps) => ( +const StatsItem = ({ name, placeholder, value, onClick }: StatsItemProps) => (
-

{name}

+
+

{name}

+
-
-

{placeholder}

-
- -
-

{value}

-
+
+
+

{placeholder}

+
+
+

{value}

+
+
); export const Stats = () => { + const navigate = useNavigate(); + const handleStatClick = (filterType: string) => { + if (filterType) { + navigate(`/releases?filter=${filterType}`); + } else { + navigate("/releases"); + } + }; + const { isLoading, data } = useQuery({ queryKey: ["dash_release_stats"], queryFn: APIClient.release.stats, @@ -45,11 +61,12 @@ export const Stats = () => { Stats -
- +
+ handleStatClick("")} value={data?.filtered_count ?? 0} /> {/* */} - - + handleStatClick("PUSH_APPROVED")} value={data?.push_approved_count ?? 0} /> + handleStatClick("PUSH_REJECTED")} value={data?.push_rejected_count ?? 0 } /> + handleStatClick("PUSH_ERROR")} value={data?.push_error_count ?? 0} />
); diff --git a/web/src/screens/releases/Filters.tsx b/web/src/screens/releases/Filters.tsx index f92d25f..41a913d 100644 --- a/web/src/screens/releases/Filters.tsx +++ b/web/src/screens/releases/Filters.tsx @@ -124,8 +124,14 @@ const FilterOption = ({ label, value }: FilterOptionProps) => ( ); export const PushStatusSelectColumnFilter = ({ - column: { filterValue, setFilter, id } + column: { filterValue, setFilter, id }, + initialFilterValue }: FilterProps) => { + React.useEffect(() => { + if (initialFilterValue) { + setFilter(initialFilterValue); + } + }, [initialFilterValue, setFilter]); const label = filterValue ? PushStatusOptions.find((o) => o.value === filterValue && o.value)?.label : "Push status"; return (
diff --git a/web/src/screens/releases/ReleaseTable.tsx b/web/src/screens/releases/ReleaseTable.tsx index 3c95767..70d73b3 100644 --- a/web/src/screens/releases/ReleaseTable.tsx +++ b/web/src/screens/releases/ReleaseTable.tsx @@ -4,6 +4,7 @@ */ import React, { useState } from "react"; +import { useLocation } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table"; import { @@ -23,9 +24,7 @@ import * as DataTable from "@components/data-table"; import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./Filters"; import { classNames } from "@utils"; -import { ArrowTopRightOnSquareIcon, ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { Tooltip } from "@components/tooltips/Tooltip"; -import { ExternalLink } from "@components/ExternalLink"; export const releaseKeys = { all: ["releases"] as const, @@ -84,6 +83,9 @@ const TableReducer = (state: TableState, action: Actions): TableState => { }; export const ReleaseTable = () => { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + const filterTypeFromUrl = queryParams.get("filter"); const columns = React.useMemo(() => [ { Header: "Age", @@ -110,26 +112,17 @@ export const ReleaseTable = () => { {String(props.cell.value)} -
- {props.row.original.download_url && ( - - - )} - {props.row.original.info_url && ( - - - )} -
); }, Filter: SearchColumnFilter }, + { + Header: "Links", + accessor: (row) => ({ download_url: row.download_url, info_url: row.info_url }), + id: "links", + Cell: DataTable.LinksCell + }, { Header: "Actions", accessor: "action_status", @@ -199,7 +192,7 @@ export const ReleaseTable = () => { initialState: { pageIndex: queryPageIndex, pageSize: queryPageSize, - filters: [] + filters: filterTypeFromUrl ? [{ id: "action_status", value: filterTypeFromUrl }] : [] }, manualPagination: true, manualFilters: true, diff --git a/web/src/types/Release.d.ts b/web/src/types/Release.d.ts index e5e4eb1..756faba 100644 --- a/web/src/types/Release.d.ts +++ b/web/src/types/Release.d.ts @@ -45,6 +45,7 @@ interface ReleaseStats { filter_rejected_count: number; push_approved_count: number; push_rejected_count: number; + push_error_count: number; } interface ReleaseFilter {