From 0f0108900b0f6012df9a17cf922f0543375ac599 Mon Sep 17 00:00:00 2001 From: blinkeye-lcm Date: Tue, 26 May 2026 08:56:55 +0000 Subject: [PATCH] refactor: consolidate PR and issue status filter guards into shared utils --- .../miners/MinerOpenDiscoveryIssuesByRepo.tsx | 15 ++------------- src/components/miners/MinerPRsTable.tsx | 11 +---------- .../repositories/RepositoryPRsTable.tsx | 10 ++++++---- src/pages/WatchlistPage.tsx | 9 ++++----- src/utils/issueStatus.ts | 15 +++++++++++++++ src/utils/prStatus.ts | 13 +++++++++++++ src/utils/prTable.ts | 9 ++++++--- 7 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/components/miners/MinerOpenDiscoveryIssuesByRepo.tsx b/src/components/miners/MinerOpenDiscoveryIssuesByRepo.tsx index 7cd3a403..8523641f 100644 --- a/src/components/miners/MinerOpenDiscoveryIssuesByRepo.tsx +++ b/src/components/miners/MinerOpenDiscoveryIssuesByRepo.tsx @@ -18,8 +18,10 @@ import type { MinerIssue } from '../../api/models/Dashboard'; import { getRepositoryOwnerAvatarSrc, getScoringWindowStartIso, + isIssueStatusFilter, isOutsideScoringWindow, paginateItems, + type IssueStatusFilter, } from '../../utils'; import { DataTable, @@ -30,19 +32,11 @@ import { ClearSearchAdornment } from '../common/ClearSearchAdornment'; import TablePagination from '../common/TablePagination'; import { tooltipSlotProps } from '../../theme'; -type IssueStatusFilter = 'all' | 'open' | 'solved' | 'closed'; type IssueSortField = 'number' | 'repository' | 'date'; type SortDir = 'asc' | 'desc'; const PAGE_SIZE = 20; -const ISSUE_STATUS_FILTERS: readonly IssueStatusFilter[] = [ - 'all', - 'open', - 'solved', - 'closed', -]; - const DEFAULT_SORT_DIR: Record = { number: 'desc', repository: 'asc', @@ -55,11 +49,6 @@ const isSolvedIssue = (i: MinerIssue) => const isClosedIssue = (i: MinerIssue) => i.state === 'CLOSED' && !i.solving_pr?.merged_at; -const isIssueStatusFilter = ( - value: string | null, -): value is IssueStatusFilter => - value !== null && (ISSUE_STATUS_FILTERS as readonly string[]).includes(value); - const filterIssues = ( issues: MinerIssue[], { diff --git a/src/components/miners/MinerPRsTable.tsx b/src/components/miners/MinerPRsTable.tsx index 37ca8d02..9dc295c8 100644 --- a/src/components/miners/MinerPRsTable.tsx +++ b/src/components/miners/MinerPRsTable.tsx @@ -31,6 +31,7 @@ import { getRepositoryOwnerAvatarSrc, getPrStatusCounts, isOutsideScoringWindow, + isPrStatusFilter, paginateItems, type PrStatusFilter, } from '../../utils'; @@ -63,13 +64,6 @@ type SortDir = 'asc' | 'desc'; const PAGE_SIZE = 20; -const PR_STATUS_FILTERS: readonly PrStatusFilter[] = [ - 'all', - 'open', - 'merged', - 'closed', -]; - // Direction applied when a user first clicks a column header — string // columns feel natural ascending, numeric/date columns descending. const DEFAULT_SORT_DIR: Record = { @@ -103,9 +97,6 @@ const getScoreTooltip = (pr: CommitLog): string | null => { return parts.join(' · '); }; -const isPrStatusFilter = (value: string | null): value is PrStatusFilter => - value !== null && (PR_STATUS_FILTERS as readonly string[]).includes(value); - // Stable per-PR key — shared by the DataTable row key and the expanded-row // tracking set so the two never drift. const prRowKey = (pr: CommitLog): string => diff --git a/src/components/repositories/RepositoryPRsTable.tsx b/src/components/repositories/RepositoryPRsTable.tsx index 8e763ad4..678b4737 100644 --- a/src/components/repositories/RepositoryPRsTable.tsx +++ b/src/components/repositories/RepositoryPRsTable.tsx @@ -29,7 +29,12 @@ import { useWatchlist, } from '../../hooks/useWatchlist'; import theme, { TEXT_OPACITY, scrollbarSx } from '../../theme'; -import { filterPrs, getPrStatusCounts, type PrStatusFilter } from '../../utils'; +import { + filterPrs, + getPrStatusCounts, + isPrStatusFilter, + type PrStatusFilter, +} from '../../utils'; import { getRepositoryOwnerAvatarSrc } from '../../utils/avatar'; import { formatDate } from '../../utils/format'; import FilterButton from '../FilterButton'; @@ -52,9 +57,6 @@ interface RepositoryPRsTableProps { state?: 'open' | 'closed' | 'merged' | 'all'; } -const isPrStatusFilter = (v: unknown): v is PrStatusFilter => - v === 'all' || v === 'open' || v === 'merged' || v === 'closed'; - const PR_PAGE_SIZE = 20; const RepositoryPRsTable: React.FC = ({ diff --git a/src/pages/WatchlistPage.tsx b/src/pages/WatchlistPage.tsx index fe91a4c7..15fc24fd 100644 --- a/src/pages/WatchlistPage.tsx +++ b/src/pages/WatchlistPage.tsx @@ -102,9 +102,11 @@ import { usePrSourceFilter } from '../hooks/usePrSourceFilter'; import { isMergedPr, isClosedUnmergedPr, + isPrStatusFilter, getPrStatusCounts, + type PrStatusFilter, } from '../utils/prStatus'; -import { filterPrs, type PrStatusFilter } from '../utils/prTable'; +import { filterPrs } from '../utils/prTable'; import { getIssueStatusMeta } from '../utils/issueStatus'; import { formatDate, formatTokenAmount, formatWeight } from '../utils/format'; import { getRepositoryOwnerAvatarSrc } from '../utils/avatar'; @@ -1029,9 +1031,6 @@ type WatchedRepoStats = Repository & { discoveryContributors: Set; }; -const isPrStatusFilterStored = (v: unknown): v is PrStatusFilter => - v === 'all' || v === 'open' || v === 'merged' || v === 'closed'; - type RepoSortKey = | 'name' | 'weight' @@ -3238,7 +3237,7 @@ const PRsList: React.FC<{ itemKeys: string[] }> = ({ itemKeys }) => { const [statusFilter, setStatusFilter] = useSessionStoredState( 'watchlist:prs:statusFilter', 'all', - isPrStatusFilterStored, + isPrStatusFilter, ); const [viewMode, setViewMode] = useWatchlistViewMode(); const [page, setPage] = useState(0); diff --git a/src/utils/issueStatus.ts b/src/utils/issueStatus.ts index 9dcd274a..af72c51f 100644 --- a/src/utils/issueStatus.ts +++ b/src/utils/issueStatus.ts @@ -1,6 +1,21 @@ import { alpha } from '@mui/material/styles'; import { STATUS_COLORS } from '../theme'; +export type IssueStatusFilter = 'all' | 'open' | 'solved' | 'closed'; + +export const ISSUE_STATUS_FILTERS: readonly IssueStatusFilter[] = [ + 'all', + 'open', + 'solved', + 'closed', +]; + +export const isIssueStatusFilter = ( + value: unknown, +): value is IssueStatusFilter => + typeof value === 'string' && + (ISSUE_STATUS_FILTERS as readonly string[]).includes(value); + interface IssueStatusMeta { bgColor: string; borderColor: string; diff --git a/src/utils/prStatus.ts b/src/utils/prStatus.ts index ac927563..362054ba 100644 --- a/src/utils/prStatus.ts +++ b/src/utils/prStatus.ts @@ -3,6 +3,19 @@ interface PrStatusLike { prState?: string | null; } +export type PrStatusFilter = 'all' | 'open' | 'merged' | 'closed'; + +export const PR_STATUS_FILTERS: readonly PrStatusFilter[] = [ + 'all', + 'open', + 'merged', + 'closed', +]; + +export const isPrStatusFilter = (value: unknown): value is PrStatusFilter => + typeof value === 'string' && + (PR_STATUS_FILTERS as readonly string[]).includes(value); + export const isOpenPr = (pr: PrStatusLike): boolean => pr.prState === 'OPEN' || (!pr.prState && !pr.mergedAt); diff --git a/src/utils/prTable.ts b/src/utils/prTable.ts index 1b82afba..3a0ecbbf 100644 --- a/src/utils/prTable.ts +++ b/src/utils/prTable.ts @@ -1,5 +1,10 @@ import { type CommitLog } from '../api'; -import { isClosedUnmergedPr, isMergedPr, isOpenPr } from './prStatus'; +import { + isClosedUnmergedPr, + isMergedPr, + isOpenPr, + type PrStatusFilter, +} from './prStatus'; /** Substring match against merged timestamp (ISO, locale date/time, year). */ const mergedAtMatchesSearch = ( @@ -16,8 +21,6 @@ const mergedAtMatchesSearch = ( return false; }; -export type PrStatusFilter = 'all' | 'open' | 'merged' | 'closed'; - interface FilterPrsOptions { author?: string | null; includeNumber?: boolean;