From 920295568c7f0ea7f36deba677ebc8dd4bd0f4c6 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 11 Dec 2023 22:47:10 +0000 Subject: [PATCH 01/11] chore: ignore prettier rules if installed --- who-metrics-ui/.eslintrc.js | 1 + who-metrics-ui/.prettierrc | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 who-metrics-ui/.prettierrc diff --git a/who-metrics-ui/.eslintrc.js b/who-metrics-ui/.eslintrc.js index 14d7402..926ee62 100644 --- a/who-metrics-ui/.eslintrc.js +++ b/who-metrics-ui/.eslintrc.js @@ -63,6 +63,7 @@ const baseConfig = { }, ignorePatterns: ["*__generated__*"], rules: { + "prettier/prettier": 0, // Sorry but prettier/prettier is quite problematic "import/no-unresolved": [ "error", { diff --git a/who-metrics-ui/.prettierrc b/who-metrics-ui/.prettierrc new file mode 100644 index 0000000..6d99a58 --- /dev/null +++ b/who-metrics-ui/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 120, + "tabWidth": 2 +} From 9cd66767d07a2227ad73fd8cd4d5ff2fb75d4d86 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 11 Dec 2023 22:47:18 +0000 Subject: [PATCH 02/11] feat: add repo headers --- .../src/components/RepositoriesTable.tsx | 138 +++++++++++++----- 1 file changed, 104 insertions(+), 34 deletions(-) diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 92e824f..9bfb86e 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -1,31 +1,49 @@ import { InfoIcon } from "@primer/octicons-react"; import { Tooltip } from "@primer/react"; import { Flex, Text } from "@tremor/react"; -import DataGrid, { type SortColumn } from "react-data-grid"; +import DataGrid, { Column, type RenderHeaderCellProps, type SortColumn } from "react-data-grid"; +import { createContext, useContext, useState } from "react"; import Data from "../data/data.json"; -import { useState } from "react"; const repos = Object.values(Data["repositories"]); type Repo = (typeof repos)[0]; -const Labels: Record = { - Name: "repositoryName", - Collaborators: "collaboratorsCount", - License: "licenseName", - Watchers: "watchersCount", - "Open Issues": "openIssuesCount", - "Closed Issues": "closedIssuesCount", - "Open PRs": "openPullRequestsCount", - "Merged PRs": "mergedPullRequestsCount", - Forks: "forksCount", -} as const; - -const DataGridColumns = Object.keys(Labels).map((label) => { - return { - key: Labels[label], - name: label, - }; -}); +function inputStopPropagation(event: React.KeyboardEvent) { + event.stopPropagation(); +} + +function selectStopPropagation(event: React.KeyboardEvent) { + if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) { + event.stopPropagation(); + } +} + + +type Filter = Repo + +function FilterRenderer({ + tabIndex, + column, + children +}: RenderHeaderCellProps & { + children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; +}) { + const filters = useContext(FilterContext)!; + return ( +
+
{column.name}
+ Hello +
{children({ tabIndex, filters })}
+
+ ); +} + +// Context is needed to read filter values otherwise columns are +// re-created when filters are changed and filter loses focus +const FilterContext = createContext(undefined); + +type Row = Record + type Comparator = (a: Repo, b: Repo) => number; @@ -67,10 +85,61 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { }; const RepositoriesTable = () => { + const [globalFilters, setGlobalFilters] = useState( + {} as Filter + ); + + + // This needs a type, technically it's a Column but needs to be typed + const labels: Record> = { + Name: { + key: "repositoryName", + name: "Name", + headerCellClass: "h-32", + renderHeaderCell: (p) => { + console.log(p) + return {...(p as any)}> + {({ filters, ...rest }) => ( + + setGlobalFilters((otherFilters) => ({ + ...otherFilters, + repositoryName: e.target.value + })) + } + onKeyDown={inputStopPropagation} + onClick={e => e.stopPropagation()} + /> + )} + + } + }, + // Collaborators: "collaboratorsCount", + // License: "licenseName", + // Watchers: "watchersCount", + // "Open Issues": "openIssuesCount", + // "Closed Issues": "closedIssuesCount", + // "Open PRs": "openPullRequestsCount", + // "Merged PRs": "mergedPullRequestsCount", + // Forks: "forksCount", + } as const; + + + const dataGridColumns = Object.entries(labels).map(([_, columnProps]) => columnProps); + const subTitle = () => { return `${repos.length} total repositories`; }; + // This selects a field to populate a dropdown with + const dropdownOptions = (field: keyof Repo) => + Array.from(new Set(repos.map((r) => r[field]))).map((d) => ({ + label: d, + value: d, + })); + const [sortColumns, setSortColumns] = useState([]); const sortedRepos = () => { @@ -104,20 +173,21 @@ const RepositoriesTable = () => { {subTitle()} -
- repo.repoName} - defaultColumnOptions={{ - sortable: true, - resizable: true, - }} - sortColumns={sortColumns} - onSortColumnsChange={setSortColumns} - style={{ height: "100%", width: "100%" }} - /> -
+ +
+ repo.repoName} + defaultColumnOptions={{ + sortable: true, + resizable: true, + }} + sortColumns={sortColumns} + onSortColumnsChange={setSortColumns} + style={{ height: "100%", width: "100%" }} + /> +
); }; From 1ff3f9edfb86d2002907938c3b660610e5cbe43b Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Fri, 12 Jan 2024 14:16:15 -0500 Subject: [PATCH 03/11] feat: update headers --- who-metrics-ui/.vscode/launch.json | 28 ++++ ...boardExample.tsx => OrganizationSheet.tsx} | 54 +------ .../src/components/RepositoriesTable.tsx | 145 ++++++++++-------- who-metrics-ui/src/components/index.ts | 4 +- who-metrics-ui/src/pages/index.tsx | 4 +- 5 files changed, 118 insertions(+), 117 deletions(-) create mode 100644 who-metrics-ui/.vscode/launch.json rename who-metrics-ui/src/components/{DashboardExample.tsx => OrganizationSheet.tsx} (62%) diff --git a/who-metrics-ui/.vscode/launch.json b/who-metrics-ui/.vscode/launch.json new file mode 100644 index 0000000..ee3bdd7 --- /dev/null +++ b/who-metrics-ui/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + { + "name": "Next.js: debug client-side", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "serverReadyAction": { + "pattern": "- Local:.+(https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] +} diff --git a/who-metrics-ui/src/components/DashboardExample.tsx b/who-metrics-ui/src/components/OrganizationSheet.tsx similarity index 62% rename from who-metrics-ui/src/components/DashboardExample.tsx rename to who-metrics-ui/src/components/OrganizationSheet.tsx index 90203c1..1a1ade6 100644 --- a/who-metrics-ui/src/components/DashboardExample.tsx +++ b/who-metrics-ui/src/components/OrganizationSheet.tsx @@ -1,58 +1,20 @@ "use client"; import { - Title, - Text, - Tab, - TabList, - TabGroup, - TabPanel, - TabPanels, + Tab, TabGroup, TabList, TabPanel, + TabPanels, Text, Title } from "@tremor/react"; +import logo from "@/images/who-logo-wide.svg"; import { Box, useTheme as primerUseTheme } from "@primer/react"; import Image from "next/image"; -import logo from "@/images/who-logo-wide.svg"; -import RepositoriesTable from "./RepositoriesTable"; -import Data from "../data/data.json"; import { useTheme } from "next-themes"; +import data from "../data/data.json"; +import RepositoriesTable from "./RepositoriesTable"; -export type DailyPerformance = { - date: string; - Sales: number; - Profit: number; - Customers: number; -}; - -export const performance: DailyPerformance[] = [ - { - date: "2023-05-01", - Sales: 900.73, - Profit: 173, - Customers: 73, - }, - { - date: "2023-05-02", - Sales: 1000.74, - Profit: 174.6, - Customers: 74, - }, - { - date: "2023-05-03", - Sales: 1100.93, - Profit: 293.1, - Customers: 293, - }, - { - date: "2023-05-04", - Sales: 1200.9, - Profit: 290.2, - Customers: 29, - }, -]; -export const DashboardExample = () => { +export const OrganizationSheet = () => { const { theme, systemTheme } = useTheme(); const { setColorMode } = primerUseTheme(); if (theme === "light" || theme === "dark" || theme === "auto") { @@ -76,11 +38,11 @@ export const DashboardExample = () => { width={150} alt="World Health Organization logo" /> - {Data.orgInfo.name} Open Source Dashboard + {data.orgInfo.name} Open Source Dashboard This project includes metrics about the Open Source repositories for the - {Data.orgInfo.name}. + {data.orgInfo.name}. diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 9bfb86e..3cfec25 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -1,11 +1,11 @@ -import { InfoIcon } from "@primer/octicons-react"; -import { Tooltip } from "@primer/react"; -import { Flex, Text } from "@tremor/react"; -import DataGrid, { Column, type RenderHeaderCellProps, type SortColumn } from "react-data-grid"; - -import { createContext, useContext, useState } from "react"; -import Data from "../data/data.json"; -const repos = Object.values(Data["repositories"]); +import { InfoIcon } from '@primer/octicons-react'; +import { Tooltip } from '@primer/react'; +import { Flex, Text } from '@tremor/react'; +import DataGrid, { Column, type RenderHeaderCellProps, type SortColumn } from 'react-data-grid'; + +import { createContext, useCallback, useContext, useState } from 'react'; +import Data from '../data/data.json'; +const repos = Object.values(Data['repositories']); type Repo = (typeof repos)[0]; function inputStopPropagation(event: React.KeyboardEvent) { @@ -18,19 +18,20 @@ function selectStopPropagation(event: React.KeyboardEvent) { } } - -type Filter = Repo +type Filter = { + repositoryName?: string; +}; function FilterRenderer({ tabIndex, column, - children + children, }: RenderHeaderCellProps & { children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; }) { const filters = useContext(FilterContext)!; return ( -
+
{column.name}
Hello
{children({ tabIndex, filters })}
@@ -42,24 +43,23 @@ function FilterRenderer({ // re-created when filters are changed and filter loses focus const FilterContext = createContext(undefined); -type Row = Record - +type Row = Record; type Comparator = (a: Repo, b: Repo) => number; const getComparator = (sortColumn: keyof Repo): Comparator => { switch (sortColumn) { // number based sorting - case "closedIssuesCount": - case "collaboratorsCount": - case "discussionsCount": - case "forksCount": - case "issuesCount": - case "mergedPullRequestsCount": - case "openIssuesCount": - case "openPullRequestsCount": - case "projectsCount": - case "watchersCount": + case 'closedIssuesCount': + case 'collaboratorsCount': + case 'discussionsCount': + case 'forksCount': + case 'issuesCount': + case 'mergedPullRequestsCount': + case 'openIssuesCount': + case 'openPullRequestsCount': + case 'projectsCount': + case 'watchersCount': return (a, b) => { if (a[sortColumn] === b[sortColumn]) { return 0; @@ -73,11 +73,11 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { }; // alphabetical sorting - case "licenseName": - case "repoName": - case "repositoryName": + case 'licenseName': + case 'repoName': + case 'repositoryName': return (a, b) => { - return a[sortColumn].localeCompare(b[sortColumn]); + return a[sortColumn].toLowerCase().localeCompare(b[sortColumn].toLowerCase()); }; default: throw new Error(`unsupported sortColumn: "${sortColumn}"`); @@ -85,36 +85,37 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { }; const RepositoriesTable = () => { - const [globalFilters, setGlobalFilters] = useState( - {} as Filter - ); - + const [globalFilters, setGlobalFilters] = useState({ + repositoryName: undefined, + } as Filter); // This needs a type, technically it's a Column but needs to be typed const labels: Record> = { Name: { - key: "repositoryName", - name: "Name", - headerCellClass: "h-32", - renderHeaderCell: (p) => { - console.log(p) - return {...(p as any)}> - {({ filters, ...rest }) => ( - - setGlobalFilters((otherFilters) => ({ - ...otherFilters, - repositoryName: e.target.value - })) - } - onKeyDown={inputStopPropagation} - onClick={e => e.stopPropagation()} - /> - )} - - } + key: 'repositoryName', + name: 'Name', + headerCellClass: 'h-32', + // renderHeaderCell: (p) => { + // console.log(p); + // return ( + // {...(p as any)}> + // {({ filters, ...rest }) => ( + // + // setGlobalFilters((otherFilters) => ({ + // ...otherFilters, + // repositoryName: e.target.value, + // })) + // } + // onKeyDown={inputStopPropagation} + // onClick={(e) => e.stopPropagation()} + // /> + // )} + // + // ); + // }, }, // Collaborators: "collaboratorsCount", // License: "licenseName", @@ -126,7 +127,6 @@ const RepositoriesTable = () => { // Forks: "forksCount", } as const; - const dataGridColumns = Object.entries(labels).map(([_, columnProps]) => columnProps); const subTitle = () => { @@ -142,15 +142,17 @@ const RepositoriesTable = () => { const [sortColumns, setSortColumns] = useState([]); - const sortedRepos = () => { - if (sortColumns.length === 0) return repos; + const sortRepos = (inputRepos: Repo[]) => { + if (sortColumns.length === 0) { + return repos; + } - const sortedRows = [...repos].sort((a, b) => { + const sortedRows = [...inputRepos].sort((a, b) => { for (const sort of sortColumns) { const comparator = getComparator(sort.columnKey as keyof Repo); const compResult = comparator(a, b); if (compResult !== 0) { - return sort.direction === "ASC" ? compResult : -compResult; + return sort.direction === 'ASC' ? compResult : -compResult; } } return 0; @@ -159,14 +161,21 @@ const RepositoriesTable = () => { return sortedRows; }; + const filterRepos = useCallback( + (inputRepos: Repo[]) => { + const result = inputRepos.filter((repo) => { + return globalFilters.repositoryName ? repo.repositoryName.includes(globalFilters.repositoryName) : true; + }); + + return result; + }, + [globalFilters], + ); + return (
- + @@ -176,8 +185,9 @@ const RepositoriesTable = () => {
repo.repoName} defaultColumnOptions={{ sortable: true, @@ -185,9 +195,10 @@ const RepositoriesTable = () => { }} sortColumns={sortColumns} onSortColumnsChange={setSortColumns} - style={{ height: "100%", width: "100%" }} + style={{ height: '100%', width: '100%' }} /> -
+
+
); }; diff --git a/who-metrics-ui/src/components/index.ts b/who-metrics-ui/src/components/index.ts index 9b09fe5..bdd9366 100644 --- a/who-metrics-ui/src/components/index.ts +++ b/who-metrics-ui/src/components/index.ts @@ -1,2 +1,2 @@ -export { ChartView } from "./ChartView"; -export { DashboardExample } from "./DashboardExample"; +export { ChartView } from './ChartView'; +export { OrganizationSheet } from './OrganizationSheet'; diff --git a/who-metrics-ui/src/pages/index.tsx b/who-metrics-ui/src/pages/index.tsx index e8568d0..2374d8e 100644 --- a/who-metrics-ui/src/pages/index.tsx +++ b/who-metrics-ui/src/pages/index.tsx @@ -1,10 +1,10 @@ /* eslint-disable filenames/match-regex */ -import { DashboardExample } from "../components"; +import { OrganizationSheet } from "../components/OrganizationSheet"; export default function PlaygroundPage() { return (
- +
); } From aea388cd7a728242de91b3006c0aac085bb4d244 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Fri, 12 Jan 2024 14:53:00 -0500 Subject: [PATCH 04/11] feat: add header for license type --- .../src/components/RepositoriesTable.tsx | 129 +++++++++++++----- 1 file changed, 98 insertions(+), 31 deletions(-) diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 3cfec25..aeded45 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -20,31 +20,41 @@ function selectStopPropagation(event: React.KeyboardEvent) { type Filter = { repositoryName?: string; + licenseName?: string[]; }; -function FilterRenderer({ +/** + * Wrapper for rendering column header cell + * @param { + * tabIndex: number; + * column: Column; + * children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; + * } props + * @returns + */ +const FilterRenderer = ({ tabIndex, column, - children, + children: filterFunction, + sortDirection, }: RenderHeaderCellProps & { children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; -}) { +}) => { const filters = useContext(FilterContext)!; + return (
{column.name}
- Hello -
{children({ tabIndex, filters })}
+
{sortDirection === 'ASC' ? 'UP' : sortDirection === 'DESC' ? 'DOWN' : null}
+
{filterFunction({ tabIndex, filters })}
); -} +}; // Context is needed to read filter values otherwise columns are // re-created when filters are changed and filter loses focus const FilterContext = createContext(undefined); -type Row = Record; - type Comparator = (a: Repo, b: Repo) => number; const getComparator = (sortColumn: keyof Repo): Comparator => { @@ -87,6 +97,7 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { const RepositoriesTable = () => { const [globalFilters, setGlobalFilters] = useState({ repositoryName: undefined, + licenseName: [], } as Filter); // This needs a type, technically it's a Column but needs to be typed @@ -95,30 +106,74 @@ const RepositoriesTable = () => { key: 'repositoryName', name: 'Name', headerCellClass: 'h-32', - // renderHeaderCell: (p) => { - // console.log(p); - // return ( - // {...(p as any)}> - // {({ filters, ...rest }) => ( - // - // setGlobalFilters((otherFilters) => ({ - // ...otherFilters, - // repositoryName: e.target.value, - // })) - // } - // onKeyDown={inputStopPropagation} - // onClick={(e) => e.stopPropagation()} - // /> - // )} - // - // ); - // }, + renderHeaderCell: (p) => { + return ( + {...p}> + {({ filters, ...rest }) => ( + + setGlobalFilters((otherFilters) => ({ + ...otherFilters, + repositoryName: e.target.value, + })) + } + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} + /> + )} + + ); + }, + }, + License: { + key: 'licenseName', + name: 'License', + headerCellClass: 'h-32', + renderHeaderCell: (p) => { + return ( + {...p}> + {({ filters, ...rest }) => ( + + )} + + ); + }, }, // Collaborators: "collaboratorsCount", - // License: "licenseName", // Watchers: "watchersCount", // "Open Issues": "openIssuesCount", // "Closed Issues": "closedIssuesCount", @@ -161,10 +216,22 @@ const RepositoriesTable = () => { return sortedRows; }; + /** + * Uses globalFilters to filter the repos that are then passed to sortRepos + * + * NOTE: We use some hacks like addings 'all' to the licenseName filter to + * make it easier to filter the repos. + */ const filterRepos = useCallback( (inputRepos: Repo[]) => { const result = inputRepos.filter((repo) => { - return globalFilters.repositoryName ? repo.repositoryName.includes(globalFilters.repositoryName) : true; + return ( + (globalFilters.repositoryName ? repo.repositoryName.includes(globalFilters.repositoryName) : true) && + ((globalFilters.licenseName?.length ?? 0 > 0 + ? globalFilters.licenseName?.includes(repo.licenseName) + : true) || + globalFilters.licenseName?.includes('all')) + ); }); return result; From 5e4921c0371ba29de146af58c8a8d98be271d27a Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 16 Jan 2024 16:05:52 -0500 Subject: [PATCH 05/11] feat: add collaborator count filter --- .../src/components/RepositoriesTable.tsx | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index aeded45..0dfa5af 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -21,6 +21,7 @@ function selectStopPropagation(event: React.KeyboardEvent) { type Filter = { repositoryName?: string; licenseName?: string[]; + collaboratorsCount?: Array; }; /** @@ -173,7 +174,52 @@ const RepositoriesTable = () => { ); }, }, - // Collaborators: "collaboratorsCount", + Collaborators: { + key: 'collaboratorsCount', + name: 'Collaborator Count', + headerCellClass: 'h-32', + renderHeaderCell: (p) => { + return ( + {...p}> + {({ filters, ...rest }) => ( +
+ + { + console.log(e.target.value); + setGlobalFilters((otherFilters) => ({ + ...otherFilters, + collaboratorsCount: [Number(e.target.value), otherFilters.collaboratorsCount?.[1]], + })); + }} + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} + /> + + + setGlobalFilters((otherFilters) => ({ + ...otherFilters, + collaboratorsCount: [0, Number(e.target.value)], + })) + } + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} + /> +
+ )} + + ); + }, + }, // Watchers: "watchersCount", // "Open Issues": "openIssuesCount", // "Closed Issues": "closedIssuesCount", @@ -230,7 +276,11 @@ const RepositoriesTable = () => { ((globalFilters.licenseName?.length ?? 0 > 0 ? globalFilters.licenseName?.includes(repo.licenseName) : true) || - globalFilters.licenseName?.includes('all')) + globalFilters.licenseName?.includes('all')) && + (globalFilters.collaboratorsCount + ? (globalFilters.collaboratorsCount?.[0] ?? 0) <= repo.collaboratorsCount && + repo.collaboratorsCount <= (globalFilters.collaboratorsCount[1] ?? Infinity) + : true) ); }); From 3d2376598823bf41095e9f99a86a72f01eb73bd2 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 22 Jan 2024 15:47:21 -0500 Subject: [PATCH 06/11] feat: add header popovers to all columns --- who-metrics-ui/package-lock.json | 10 + who-metrics-ui/package.json | 1 + .../src/components/RepositoriesTable.tsx | 270 ++++++++++++++---- 3 files changed, 228 insertions(+), 53 deletions(-) diff --git a/who-metrics-ui/package-lock.json b/who-metrics-ui/package-lock.json index 0d30abe..f137533 100644 --- a/who-metrics-ui/package-lock.json +++ b/who-metrics-ui/package-lock.json @@ -33,6 +33,7 @@ "react": "^18.2.0", "react-data-grid": "^7.0.0-beta.41", "react-dom": "^18.2.0", + "react-tiny-popover": "^8.0.4", "server-only": "^0.0.1", "styled-components": "^5.3.11", "tailwindcss": "^3.2.7", @@ -5140,6 +5141,15 @@ "react-dom": ">=15.0.0" } }, + "node_modules/react-tiny-popover": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/react-tiny-popover/-/react-tiny-popover-8.0.4.tgz", + "integrity": "sha512-pn0Y/G0gyMdYTBEWSKCCnaZsXAa54PkfnRE4fnMM5633SSClYrXxwXKc6vPYgJ9shLatGginxMjnhXq6guZmng==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/who-metrics-ui/package.json b/who-metrics-ui/package.json index 7f56a84..14a08be 100644 --- a/who-metrics-ui/package.json +++ b/who-metrics-ui/package.json @@ -34,6 +34,7 @@ "react": "^18.2.0", "react-data-grid": "^7.0.0-beta.41", "react-dom": "^18.2.0", + "react-tiny-popover": "^8.0.4", "server-only": "^0.0.1", "styled-components": "^5.3.11", "tailwindcss": "^3.2.7", diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 0dfa5af..80bdab6 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -1,9 +1,15 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable primer-react/no-system-props */ +/* eslint-disable react/jsx-no-comment-textnodes */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable react/no-unescaped-entities */ import { InfoIcon } from '@primer/octicons-react'; -import { Tooltip } from '@primer/react'; +import { Button, Tooltip } from '@primer/react'; import { Flex, Text } from '@tremor/react'; import DataGrid, { Column, type RenderHeaderCellProps, type SortColumn } from 'react-data-grid'; +import { Popover } from 'react-tiny-popover'; -import { createContext, useCallback, useContext, useState } from 'react'; +import { createContext, FC, useCallback, useContext, useRef, useState } from 'react'; import Data from '../data/data.json'; const repos = Object.values(Data['repositories']); type Repo = (typeof repos)[0]; @@ -22,6 +28,58 @@ type Filter = { repositoryName?: string; licenseName?: string[]; collaboratorsCount?: Array; + watchersCount?: Array; + openIssuesCount?: Array; + openPullRequestsCount?: Array; + closedIssuesCount?: Array; + mergedPullRequestsCount?: Array; + forksCount?: Array; +}; + +const MinMaxRenderer: FC<{ + headerCellProps: RenderHeaderCellProps; + filters: Filter; + updateFilters: ((filters: Filter) => void) & ((filters: (filters: Filter) => Filter) => void); + filterName: keyof Filter; +}> = ({ headerCellProps, filters, updateFilters, filterName }) => { + return ( + {...headerCellProps}> + {({ ...rest }) => ( +
+ + { + updateFilters((globalFilters) => ({ + ...globalFilters, + [filterName]: [Number(e.target.value), globalFilters[filterName]?.[1]], + })); + }} + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} + /> + + + updateFilters({ + ...filters, + [filterName]: [0, Number(e.target.value)], + }) + } + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} + /> +
+ )} + + ); }; /** @@ -42,12 +100,42 @@ const FilterRenderer = ({ children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; }) => { const filters = useContext(FilterContext)!; + const clickMeButtonRef = useRef(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( -
+
{column.name}
-
{sortDirection === 'ASC' ? 'UP' : sortDirection === 'DESC' ? 'DOWN' : null}
-
{filterFunction({ tabIndex, filters })}
+
+ {sortDirection === 'ASC' ? 'UP' : sortDirection === 'DESC' ? 'DOWN' : null} + setIsPopoverOpen(false)} + ref={clickMeButtonRef} // if you'd like a ref to your popover's child, you can grab one here + content={() => ( +
e.stopPropagation()}> +
+

Filter {column.name}

+ {filterFunction({ tabIndex, filters })} +
+
+ )} + > + +
+
); }; @@ -106,7 +194,7 @@ const RepositoriesTable = () => { Name: { key: 'repositoryName', name: 'Name', - headerCellClass: 'h-32', + renderHeaderCell: (p) => { return ( {...p}> @@ -131,7 +219,7 @@ const RepositoriesTable = () => { License: { key: 'licenseName', name: 'License', - headerCellClass: 'h-32', + renderHeaderCell: (p) => { return ( {...p}> @@ -177,55 +265,107 @@ const RepositoriesTable = () => { Collaborators: { key: 'collaboratorsCount', name: 'Collaborator Count', - headerCellClass: 'h-32', renderHeaderCell: (p) => { return ( - {...p}> - {({ filters, ...rest }) => ( -
- - { - console.log(e.target.value); - setGlobalFilters((otherFilters) => ({ - ...otherFilters, - collaboratorsCount: [Number(e.target.value), otherFilters.collaboratorsCount?.[1]], - })); - }} - onKeyDown={inputStopPropagation} - onClick={(e) => e.stopPropagation()} - /> - - - setGlobalFilters((otherFilters) => ({ - ...otherFilters, - collaboratorsCount: [0, Number(e.target.value)], - })) - } - onKeyDown={inputStopPropagation} - onClick={(e) => e.stopPropagation()} - /> -
- )} - + + ); + }, + }, + Watchers: { + key: 'watchersCount', + name: 'Watchers Count', + + renderHeaderCell: (p) => { + return ( + + ); + }, + }, + 'Open Issues': { + key: 'openIssuesCount', + name: 'Issues Count', + + renderHeaderCell: (p) => { + return ( + + ); + }, + }, + 'Closed Issues': { + key: 'closedIssuesCount', + name: 'Closed Issues Count', + + renderHeaderCell: (p) => { + return ( + + ); + }, + }, + 'Open PRs': { + key: 'openPullRequestsCount', + name: 'Open PRs Count', + + renderHeaderCell: (p) => { + return ( + + ); + }, + }, + 'Merged PRs': { + key: 'mergedPullRequestsCount', + name: 'Merged PRs Count', + + renderHeaderCell: (p) => { + return ( + + ); + }, + }, + Forks: { + key: 'forksCount', + name: 'Forks Count', + + renderHeaderCell: (p) => { + return ( + ); }, }, - // Watchers: "watchersCount", - // "Open Issues": "openIssuesCount", - // "Closed Issues": "closedIssuesCount", - // "Open PRs": "openPullRequestsCount", - // "Merged PRs": "mergedPullRequestsCount", - // Forks: "forksCount", } as const; const dataGridColumns = Object.entries(labels).map(([_, columnProps]) => columnProps); @@ -265,7 +405,7 @@ const RepositoriesTable = () => { /** * Uses globalFilters to filter the repos that are then passed to sortRepos * - * NOTE: We use some hacks like addings 'all' to the licenseName filter to + * NOTE: We use some hacks like adding 'all' to the licenseName filter to * make it easier to filter the repos. */ const filterRepos = useCallback( @@ -280,6 +420,30 @@ const RepositoriesTable = () => { (globalFilters.collaboratorsCount ? (globalFilters.collaboratorsCount?.[0] ?? 0) <= repo.collaboratorsCount && repo.collaboratorsCount <= (globalFilters.collaboratorsCount[1] ?? Infinity) + : true) && + (globalFilters.watchersCount + ? (globalFilters.watchersCount?.[0] ?? 0) <= repo.watchersCount && + repo.watchersCount <= (globalFilters.watchersCount[1] ?? Infinity) + : true) && + (globalFilters.openIssuesCount + ? (globalFilters.openIssuesCount?.[0] ?? 0) <= repo.openIssuesCount && + repo.openIssuesCount <= (globalFilters.openIssuesCount[1] ?? Infinity) + : true) && + (globalFilters.closedIssuesCount + ? (globalFilters.closedIssuesCount?.[0] ?? 0) <= repo.closedIssuesCount && + repo.closedIssuesCount <= (globalFilters.closedIssuesCount[1] ?? Infinity) + : true) && + (globalFilters.openPullRequestsCount + ? (globalFilters.openPullRequestsCount?.[0] ?? 0) <= repo.openPullRequestsCount && + repo.openPullRequestsCount <= (globalFilters.openPullRequestsCount[1] ?? Infinity) + : true) && + (globalFilters.mergedPullRequestsCount + ? (globalFilters.mergedPullRequestsCount?.[0] ?? 0) <= repo.mergedPullRequestsCount && + repo.mergedPullRequestsCount <= (globalFilters.mergedPullRequestsCount[1] ?? Infinity) + : true) && + (globalFilters.forksCount + ? (globalFilters.forksCount?.[0] ?? 0) <= repo.forksCount && + repo.forksCount <= (globalFilters.forksCount[1] ?? Infinity) : true) ); }); @@ -300,7 +464,7 @@ const RepositoriesTable = () => {
-
+
Date: Tue, 23 Jan 2024 15:09:05 -0500 Subject: [PATCH 07/11] feat: various clean up and adding styles to filters --- .../src/components/RepositoriesTable.tsx | 303 +++++++++++------- 1 file changed, 181 insertions(+), 122 deletions(-) diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 80bdab6..7ee5109 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -1,11 +1,8 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable primer-react/no-system-props */ -/* eslint-disable react/jsx-no-comment-textnodes */ /* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable react/no-unescaped-entities */ -import { InfoIcon } from '@primer/octicons-react'; -import { Button, Tooltip } from '@primer/react'; -import { Flex, Text } from '@tremor/react'; +import { InfoIcon, TriangleDownIcon, TriangleUpIcon, XIcon } from '@primer/octicons-react'; +import { ActionList, Box, Button, Checkbox, FormControl, TextInput, Tooltip } from '@primer/react'; +import { Text } from '@tremor/react'; import DataGrid, { Column, type RenderHeaderCellProps, type SortColumn } from 'react-data-grid'; import { Popover } from 'react-tiny-popover'; @@ -18,15 +15,9 @@ function inputStopPropagation(event: React.KeyboardEvent) { event.stopPropagation(); } -function selectStopPropagation(event: React.KeyboardEvent) { - if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) { - event.stopPropagation(); - } -} - type Filter = { repositoryName?: string; - licenseName?: string[]; + licenseName?: Record; collaboratorsCount?: Array; watchersCount?: Array; openIssuesCount?: Array; @@ -36,6 +27,7 @@ type Filter = { forksCount?: Array; }; +// Renderer for the min/max filter inputs const MinMaxRenderer: FC<{ headerCellProps: RenderHeaderCellProps; filters: Filter; @@ -46,51 +38,43 @@ const MinMaxRenderer: FC<{ {...headerCellProps}> {({ ...rest }) => (
- - { - updateFilters((globalFilters) => ({ - ...globalFilters, - [filterName]: [Number(e.target.value), globalFilters[filterName]?.[1]], - })); - }} - onKeyDown={inputStopPropagation} - onClick={(e) => e.stopPropagation()} - /> - - - updateFilters({ - ...filters, - [filterName]: [0, Number(e.target.value)], - }) - } - onKeyDown={inputStopPropagation} - onClick={(e) => e.stopPropagation()} - /> + + Min + { + updateFilters((globalFilters) => ({ + ...globalFilters, + [filterName]: [Number(e.target.value), (globalFilters[filterName] as Array)?.[1]], + })); + }} + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} /> + + + Max + + updateFilters({ + ...filters, + [filterName]: [0, Number(e.target.value)], + }) + } + onKeyDown={inputStopPropagation} + onClick={(e) => e.stopPropagation()} /> +
)} ); }; -/** - * Wrapper for rendering column header cell - * @param { - * tabIndex: number; - * column: Column; - * children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; - * } props - * @returns - */ +// Wrapper for rendering column header cell const FilterRenderer = ({ tabIndex, column, @@ -100,14 +84,18 @@ const FilterRenderer = ({ children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; }) => { const filters = useContext(FilterContext)!; - const clickMeButtonRef = useRef(); + const clickMeButtonRef = useRef(null); const [isPopoverOpen, setIsPopoverOpen] = useState(false); return (
{column.name}
- {sortDirection === 'ASC' ? 'UP' : sortDirection === 'DESC' ? 'DOWN' : null} + {sortDirection === 'DESC' ? ( + + ) : sortDirection === 'ASC' ? ( + + ) : } ({ onClickOutside={() => setIsPopoverOpen(false)} ref={clickMeButtonRef} // if you'd like a ref to your popover's child, you can grab one here content={() => ( -
e.stopPropagation()}> -
-

Filter {column.name}

- {filterFunction({ tabIndex, filters })} +
e.stopPropagation()}> +
+ + Filter by {column.name} + {filterFunction({ tabIndex, filters })} +
)} > -
-
+
+
); }; @@ -183,11 +172,15 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { } }; +// Default set of filters +const defaultFilters: Filter = { + licenseName: { + all: true, + } +} + const RepositoriesTable = () => { - const [globalFilters, setGlobalFilters] = useState({ - repositoryName: undefined, - licenseName: [], - } as Filter); + const [globalFilters, setGlobalFilters] = useState(defaultFilters); // This needs a type, technically it's a Column but needs to be typed const labels: Record> = { @@ -199,7 +192,7 @@ const RepositoriesTable = () => { return ( {...p}> {({ filters, ...rest }) => ( - @@ -221,42 +214,91 @@ const RepositoriesTable = () => { name: 'License', renderHeaderCell: (p) => { + // This is fine because we know it's going to be rendered as a component + // eslint-disable-next-line react-hooks/rules-of-hooks + const [filteredOptions, setFilteredOptions] = useState(''); + return ( {...p}> - {({ filters, ...rest }) => ( - + return ( + <> + { + setGlobalFilters((otherFilters) => ({ + ...otherFilters, + licenseName: { + ...otherFilters.licenseName, + [d.value]: !otherFilters.licenseName?.[d.value], + }, + })); + }} + > + + + + {d.value} + + + ); + })} + + )} ); @@ -264,7 +306,7 @@ const RepositoriesTable = () => { }, Collaborators: { key: 'collaboratorsCount', - name: 'Collaborator Count', + name: 'Collaborators', renderHeaderCell: (p) => { return ( { }, Watchers: { key: 'watchersCount', - name: 'Watchers Count', + name: 'Watchers', renderHeaderCell: (p) => { return ( @@ -293,7 +335,7 @@ const RepositoriesTable = () => { }, 'Open Issues': { key: 'openIssuesCount', - name: 'Issues Count', + name: 'Open Issues', renderHeaderCell: (p) => { return ( @@ -308,7 +350,7 @@ const RepositoriesTable = () => { }, 'Closed Issues': { key: 'closedIssuesCount', - name: 'Closed Issues Count', + name: 'Closed Issues', renderHeaderCell: (p) => { return ( @@ -323,7 +365,7 @@ const RepositoriesTable = () => { }, 'Open PRs': { key: 'openPullRequestsCount', - name: 'Open PRs Count', + name: 'Open PRs', renderHeaderCell: (p) => { return ( @@ -338,7 +380,7 @@ const RepositoriesTable = () => { }, 'Merged PRs': { key: 'mergedPullRequestsCount', - name: 'Merged PRs Count', + name: 'Merged PRs', renderHeaderCell: (p) => { return ( @@ -353,7 +395,7 @@ const RepositoriesTable = () => { }, Forks: { key: 'forksCount', - name: 'Forks Count', + name: 'Total Forks', renderHeaderCell: (p) => { return ( @@ -368,6 +410,7 @@ const RepositoriesTable = () => { }, } as const; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const dataGridColumns = Object.entries(labels).map(([_, columnProps]) => columnProps); const subTitle = () => { @@ -375,11 +418,13 @@ const RepositoriesTable = () => { }; // This selects a field to populate a dropdown with - const dropdownOptions = (field: keyof Repo) => - Array.from(new Set(repos.map((r) => r[field]))).map((d) => ({ - label: d, - value: d, - })); + const dropdownOptions = (field: keyof Repo, filter = '') => + Array.from(new Set(repos.map((r) => r[field]))) + .map((d) => ({ + label: d, + value: d, + })) + .filter((d) => (d.value as string).toLowerCase().includes(filter.toLowerCase())); const [sortColumns, setSortColumns] = useState([]); @@ -407,16 +452,15 @@ const RepositoriesTable = () => { * * NOTE: We use some hacks like adding 'all' to the licenseName filter to * make it easier to filter the repos. + * + * This is kind of a mess, but it works */ const filterRepos = useCallback( (inputRepos: Repo[]) => { const result = inputRepos.filter((repo) => { return ( (globalFilters.repositoryName ? repo.repositoryName.includes(globalFilters.repositoryName) : true) && - ((globalFilters.licenseName?.length ?? 0 > 0 - ? globalFilters.licenseName?.includes(repo.licenseName) - : true) || - globalFilters.licenseName?.includes('all')) && + ((globalFilters.licenseName?.[repo.licenseName] ?? false) || (globalFilters.licenseName?.['all'] ?? false)) && (globalFilters.collaboratorsCount ? (globalFilters.collaboratorsCount?.[0] ?? 0) <= repo.collaboratorsCount && repo.collaboratorsCount <= (globalFilters.collaboratorsCount[1] ?? Infinity) @@ -455,13 +499,28 @@ const RepositoriesTable = () => { return (
-
- - - - - {subTitle()} - +
+
+
+ + + + {subTitle()} +
+
+ + + +
+
@@ -480,7 +539,7 @@ const RepositoriesTable = () => { />
-
+
); }; From 9b74f0f76b59cb3f196be1510119b4d4360907ce Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 23 Jan 2024 15:34:53 -0500 Subject: [PATCH 08/11] feat: make table fill window --- .../src/components/OrganizationSheet.tsx | 8 +++--- .../src/components/RepositoriesTable.tsx | 26 +++++++++---------- who-metrics-ui/src/pages/index.tsx | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/who-metrics-ui/src/components/OrganizationSheet.tsx b/who-metrics-ui/src/components/OrganizationSheet.tsx index 1a1ade6..0981cf5 100644 --- a/who-metrics-ui/src/components/OrganizationSheet.tsx +++ b/who-metrics-ui/src/components/OrganizationSheet.tsx @@ -26,7 +26,7 @@ export const OrganizationSheet = () => { } return ( -
+
{ This project includes metrics about the Open Source repositories for the {data.orgInfo.name}. - + Repositories - - + + diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 7ee5109..f23334b 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ import { InfoIcon, TriangleDownIcon, TriangleUpIcon, XIcon } from '@primer/octicons-react'; import { ActionList, Box, Button, Checkbox, FormControl, TextInput, Tooltip } from '@primer/react'; import { Text } from '@tremor/react'; @@ -35,7 +33,7 @@ const MinMaxRenderer: FC<{ filterName: keyof Filter; }> = ({ headerCellProps, filters, updateFilters, filterName }) => { return ( - {...headerCellProps}> + {...headerCellProps}> {({ ...rest }) => (
@@ -70,12 +68,12 @@ const MinMaxRenderer: FC<{
)} - + ); }; // Wrapper for rendering column header cell -const FilterRenderer = ({ +const HeaderCellRenderer = ({ tabIndex, column, children: filterFunction, @@ -103,6 +101,8 @@ const FilterRenderer = ({ onClickOutside={() => setIsPopoverOpen(false)} ref={clickMeButtonRef} // if you'd like a ref to your popover's child, you can grab one here content={() => ( + // The click handler here is used to stop the header from being sorted + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
e.stopPropagation()}>
@@ -190,7 +190,7 @@ const RepositoriesTable = () => { renderHeaderCell: (p) => { return ( - {...p}> + {...p}> {({ filters, ...rest }) => ( { onClick={(e) => e.stopPropagation()} /> )} - + ); }, }, @@ -219,7 +219,7 @@ const RepositoriesTable = () => { const [filteredOptions, setFilteredOptions] = useState(''); return ( - {...p}> + {...p}> {({ ...rest }) => ( { )} - + ); }, }, @@ -498,8 +498,8 @@ const RepositoriesTable = () => { ); return ( -
-
+
+
@@ -523,9 +523,9 @@ const RepositoriesTable = () => {
-
+ { /* This is a weird hack to make the table fill the page */} +
repo.repoName} diff --git a/who-metrics-ui/src/pages/index.tsx b/who-metrics-ui/src/pages/index.tsx index 2374d8e..e184397 100644 --- a/who-metrics-ui/src/pages/index.tsx +++ b/who-metrics-ui/src/pages/index.tsx @@ -3,7 +3,7 @@ import { OrganizationSheet } from "../components/OrganizationSheet"; export default function PlaygroundPage() { return ( -
+
); From 7b882aebc9ac4a3c1abbac9a92707f8102db1842 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 23 Jan 2024 15:38:06 -0500 Subject: [PATCH 09/11] feat: add prettier to dev deps --- who-metrics-ui/package-lock.json | 30 +++++++++++++++++++++++------- who-metrics-ui/package.json | 4 ++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/who-metrics-ui/package-lock.json b/who-metrics-ui/package-lock.json index f137533..d34b0f0 100644 --- a/who-metrics-ui/package-lock.json +++ b/who-metrics-ui/package-lock.json @@ -28,7 +28,6 @@ "next-auth": "^4.19.2", "next-themes": "^0.2.1", "postcss": "^8.4.31", - "prettier": "^2.8.4", "prop-types": "^15.8.1", "react": "^18.2.0", "react-data-grid": "^7.0.0-beta.41", @@ -50,7 +49,8 @@ "eslint-plugin-github": "^4.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-primer-react": "^3.0.0", - "eslint-plugin-react": "^7.32.2" + "eslint-plugin-react": "^7.32.2", + "prettier": "^3.2.4" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2933,6 +2933,21 @@ "eslint": "^8.0.1" } }, + "node_modules/eslint-plugin-github/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/eslint-plugin-i18n-text": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-i18n-text/-/eslint-plugin-i18n-text-1.0.1.tgz", @@ -4948,14 +4963,15 @@ } }, "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" diff --git a/who-metrics-ui/package.json b/who-metrics-ui/package.json index 14a08be..acf311f 100644 --- a/who-metrics-ui/package.json +++ b/who-metrics-ui/package.json @@ -29,7 +29,6 @@ "next-auth": "^4.19.2", "next-themes": "^0.2.1", "postcss": "^8.4.31", - "prettier": "^2.8.4", "prop-types": "^15.8.1", "react": "^18.2.0", "react-data-grid": "^7.0.0-beta.41", @@ -52,6 +51,7 @@ "eslint-plugin-github": "^4.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-primer-react": "^3.0.0", - "eslint-plugin-react": "^7.32.2" + "eslint-plugin-react": "^7.32.2", + "prettier": "^3.2.4" } } From b847b55806c1bfe9af670cc85e3b434636960271 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 23 Jan 2024 15:41:33 -0500 Subject: [PATCH 10/11] chore: update line width from 120 to 80 --- who-metrics-ui/.prettierrc | 2 +- .../src/components/OrganizationSheet.tsx | 32 ++-- .../src/components/RepositoriesTable.tsx | 169 +++++++++++++----- 3 files changed, 142 insertions(+), 61 deletions(-) diff --git a/who-metrics-ui/.prettierrc b/who-metrics-ui/.prettierrc index 6d99a58..e6509bf 100644 --- a/who-metrics-ui/.prettierrc +++ b/who-metrics-ui/.prettierrc @@ -1,6 +1,6 @@ { "singleQuote": true, "trailingComma": "all", - "printWidth": 120, + "printWidth": 80, "tabWidth": 2 } diff --git a/who-metrics-ui/src/components/OrganizationSheet.tsx b/who-metrics-ui/src/components/OrganizationSheet.tsx index 0981cf5..7c282cf 100644 --- a/who-metrics-ui/src/components/OrganizationSheet.tsx +++ b/who-metrics-ui/src/components/OrganizationSheet.tsx @@ -1,27 +1,31 @@ -"use client"; +'use client'; import { - Tab, TabGroup, TabList, TabPanel, - TabPanels, Text, Title -} from "@tremor/react"; + Tab, + TabGroup, + TabList, + TabPanel, + TabPanels, + Text, + Title +} from '@tremor/react'; -import logo from "@/images/who-logo-wide.svg"; -import { Box, useTheme as primerUseTheme } from "@primer/react"; -import Image from "next/image"; - -import { useTheme } from "next-themes"; -import data from "../data/data.json"; -import RepositoriesTable from "./RepositoriesTable"; +import logo from '@/images/who-logo-wide.svg'; +import { Box, useTheme as primerUseTheme } from '@primer/react'; +import Image from 'next/image'; +import { useTheme } from 'next-themes'; +import data from '../data/data.json'; +import RepositoriesTable from './RepositoriesTable'; export const OrganizationSheet = () => { const { theme, systemTheme } = useTheme(); const { setColorMode } = primerUseTheme(); - if (theme === "light" || theme === "dark" || theme === "auto") { + if (theme === 'light' || theme === 'dark' || theme === 'auto') { setColorMode(theme); } - if (theme === "system" && systemTheme) { + if (theme === 'system' && systemTheme) { setColorMode(systemTheme); } @@ -29,7 +33,7 @@ export const OrganizationSheet = () => {
; filters: Filter; - updateFilters: ((filters: Filter) => void) & ((filters: (filters: Filter) => Filter) => void); + updateFilters: ((filters: Filter) => void) & + ((filters: (filters: Filter) => Filter) => void); filterName: keyof Filter; }> = ({ headerCellProps, filters, updateFilters, filterName }) => { return ( @@ -37,23 +62,35 @@ const MinMaxRenderer: FC<{ {({ ...rest }) => (
- Min - + Min + + { updateFilters((globalFilters) => ({ ...globalFilters, - [filterName]: [Number(e.target.value), (globalFilters[filterName] as Array)?.[1]], + [filterName]: [ + Number(e.target.value), + ( + globalFilters[filterName] as Array + )?.[1], + ], })); }} onKeyDown={inputStopPropagation} - onClick={(e) => e.stopPropagation()} /> + onClick={(e) => e.stopPropagation()} + /> - Max - + Max + + e.stopPropagation()} /> + onClick={(e) => e.stopPropagation()} + />
)} @@ -93,7 +131,9 @@ const HeaderCellRenderer = ({ ) : sortDirection === 'ASC' ? ( - ) : } + ) : ( + + )} ({ content={() => ( // The click handler here is used to stop the header from being sorted // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -
e.stopPropagation()}> +
e.stopPropagation()} + >
Filter by {column.name} @@ -124,8 +167,8 @@ const HeaderCellRenderer = ({ Filters -
-
+
+
); }; @@ -165,7 +208,9 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { case 'repoName': case 'repositoryName': return (a, b) => { - return a[sortColumn].toLowerCase().localeCompare(b[sortColumn].toLowerCase()); + return a[sortColumn] + .toLowerCase() + .localeCompare(b[sortColumn].toLowerCase()); }; default: throw new Error(`unsupported sortColumn: "${sortColumn}"`); @@ -176,8 +221,8 @@ const getComparator = (sortColumn: keyof Repo): Comparator => { const defaultFilters: Filter = { licenseName: { all: true, - } -} + }, +}; const RepositoriesTable = () => { const [globalFilters, setGlobalFilters] = useState(defaultFilters); @@ -243,12 +288,18 @@ const RepositoriesTable = () => { onClick={() => { setGlobalFilters((otherFilters) => ({ ...otherFilters, - licenseName: { ...otherFilters.licenseName, all: !otherFilters.licenseName?.['all'] }, + licenseName: { + ...otherFilters.licenseName, + all: !otherFilters.licenseName?.['all'], + }, })); }} > - + All @@ -262,13 +313,19 @@ const RepositoriesTable = () => { ...otherFilters, licenseName: { ...otherFilters.licenseName, - [d.value]: !otherFilters.licenseName?.[d.value], + [d.value]: + !otherFilters.licenseName?.[d.value], }, })); }} > - + No License @@ -290,7 +347,12 @@ const RepositoriesTable = () => { }} > - + {d.value} @@ -410,8 +472,10 @@ const RepositoriesTable = () => { }, } as const; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const dataGridColumns = Object.entries(labels).map(([_, columnProps]) => columnProps); + const dataGridColumns = Object.entries(labels).map( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ([_, columnProps]) => columnProps, + ); const subTitle = () => { return `${repos.length} total repositories`; @@ -424,7 +488,9 @@ const RepositoriesTable = () => { label: d, value: d, })) - .filter((d) => (d.value as string).toLowerCase().includes(filter.toLowerCase())); + .filter((d) => + (d.value as string).toLowerCase().includes(filter.toLowerCase()), + ); const [sortColumns, setSortColumns] = useState([]); @@ -452,38 +518,51 @@ const RepositoriesTable = () => { * * NOTE: We use some hacks like adding 'all' to the licenseName filter to * make it easier to filter the repos. - * + * * This is kind of a mess, but it works */ const filterRepos = useCallback( (inputRepos: Repo[]) => { const result = inputRepos.filter((repo) => { return ( - (globalFilters.repositoryName ? repo.repositoryName.includes(globalFilters.repositoryName) : true) && - ((globalFilters.licenseName?.[repo.licenseName] ?? false) || (globalFilters.licenseName?.['all'] ?? false)) && + (globalFilters.repositoryName + ? repo.repositoryName.includes(globalFilters.repositoryName) + : true) && + ((globalFilters.licenseName?.[repo.licenseName] ?? false) || + (globalFilters.licenseName?.['all'] ?? false)) && (globalFilters.collaboratorsCount - ? (globalFilters.collaboratorsCount?.[0] ?? 0) <= repo.collaboratorsCount && - repo.collaboratorsCount <= (globalFilters.collaboratorsCount[1] ?? Infinity) + ? (globalFilters.collaboratorsCount?.[0] ?? 0) <= + repo.collaboratorsCount && + repo.collaboratorsCount <= + (globalFilters.collaboratorsCount[1] ?? Infinity) : true) && (globalFilters.watchersCount ? (globalFilters.watchersCount?.[0] ?? 0) <= repo.watchersCount && repo.watchersCount <= (globalFilters.watchersCount[1] ?? Infinity) : true) && (globalFilters.openIssuesCount - ? (globalFilters.openIssuesCount?.[0] ?? 0) <= repo.openIssuesCount && - repo.openIssuesCount <= (globalFilters.openIssuesCount[1] ?? Infinity) + ? (globalFilters.openIssuesCount?.[0] ?? 0) <= + repo.openIssuesCount && + repo.openIssuesCount <= + (globalFilters.openIssuesCount[1] ?? Infinity) : true) && (globalFilters.closedIssuesCount - ? (globalFilters.closedIssuesCount?.[0] ?? 0) <= repo.closedIssuesCount && - repo.closedIssuesCount <= (globalFilters.closedIssuesCount[1] ?? Infinity) + ? (globalFilters.closedIssuesCount?.[0] ?? 0) <= + repo.closedIssuesCount && + repo.closedIssuesCount <= + (globalFilters.closedIssuesCount[1] ?? Infinity) : true) && (globalFilters.openPullRequestsCount - ? (globalFilters.openPullRequestsCount?.[0] ?? 0) <= repo.openPullRequestsCount && - repo.openPullRequestsCount <= (globalFilters.openPullRequestsCount[1] ?? Infinity) + ? (globalFilters.openPullRequestsCount?.[0] ?? 0) <= + repo.openPullRequestsCount && + repo.openPullRequestsCount <= + (globalFilters.openPullRequestsCount[1] ?? Infinity) : true) && (globalFilters.mergedPullRequestsCount - ? (globalFilters.mergedPullRequestsCount?.[0] ?? 0) <= repo.mergedPullRequestsCount && - repo.mergedPullRequestsCount <= (globalFilters.mergedPullRequestsCount[1] ?? Infinity) + ? (globalFilters.mergedPullRequestsCount?.[0] ?? 0) <= + repo.mergedPullRequestsCount && + repo.mergedPullRequestsCount <= + (globalFilters.mergedPullRequestsCount[1] ?? Infinity) : true) && (globalFilters.forksCount ? (globalFilters.forksCount?.[0] ?? 0) <= repo.forksCount && @@ -498,8 +577,8 @@ const RepositoriesTable = () => { ); return ( -
-
+
+
@@ -517,13 +596,11 @@ const RepositoriesTable = () => { > Clear All Filters - -
- { /* This is a weird hack to make the table fill the page */} + {/* This is a weird hack to make the table fill the page */}
{ />
-
+
); }; From 5b109cb8f3614e0a3599a245b0886373c7bd5e95 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 23 Jan 2024 15:48:45 -0500 Subject: [PATCH 11/11] Update who-metrics-ui/.eslintrc.js Co-authored-by: Ian Candy --- who-metrics-ui/.eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/who-metrics-ui/.eslintrc.js b/who-metrics-ui/.eslintrc.js index 926ee62..95e1d36 100644 --- a/who-metrics-ui/.eslintrc.js +++ b/who-metrics-ui/.eslintrc.js @@ -63,7 +63,7 @@ const baseConfig = { }, ignorePatterns: ["*__generated__*"], rules: { - "prettier/prettier": 0, // Sorry but prettier/prettier is quite problematic + "prettier/prettier": 0, // We use prettier for formatting instead of ESLint "import/no-unresolved": [ "error", {