Skip to content

Commit

Permalink
Add the ability to star pull requests
Browse files Browse the repository at this point in the history
Closes #4
  • Loading branch information
pvcnt committed May 21, 2024
1 parent a5a0f60 commit 0e4ee1a
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 61 deletions.
12 changes: 6 additions & 6 deletions src/components/DashboardSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function DashboardSection({isLoading, section, isFirst, isLast, d
<div className="section-label">
<Icon icon={isCollapsed ? "chevron-down" : "chevron-up"} color="text"/>
<span className="ml-2">{section.label}</span>
{!isLoading && (count > 0) && (
{(count > 0) && (
<Tag round intent={Intent.NONE} className="ml-2">{count}</Tag>
)}
</div>
Expand All @@ -56,11 +56,11 @@ export default function DashboardSection({isLoading, section, isFirst, isLast, d
onSubmit={onChange}
onDelete={onDelete}/>

{isLoading && <Spinner/>}

{!isLoading && <Collapse isOpen={!isCollapsed}>
{count > 0 ? <PullTable data={data} /> : <p className="no-results">No results</p>}
</Collapse>}
{isLoading
? <Spinner/>
: <Collapse isOpen={!isCollapsed}>
{count > 0 ? <PullTable data={data} /> : <p className="no-results">No results</p>}
</Collapse>}
</Card>
)
}
26 changes: 22 additions & 4 deletions src/components/PullTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useContext } from "react"
import { HTMLTable, Tooltip, Tag, Icon } from "@blueprintjs/core"
import ReactTimeAgo from 'react-time-ago'
import ReactTimeAgo from "react-time-ago"

import { PullList, computeSize } from "../model"
import IconWithTooltip from "./IconWithTooltip"
import { ConfigContext } from "../config"


export type Props = {
Expand All @@ -19,10 +22,20 @@ const formatDate = (d: string) => {
}

export default function PullTable({data}: Props) {
const { config, setConfig } = useContext(ConfigContext)

const stars = new Set(config.stars)

const handleStar = (number: number) => {
setConfig(prev => prev.stars.indexOf(number) > -1
? {...prev, stars: prev.stars.filter(s => s != number)}
: {...prev, stars: prev.stars.concat([number])})
}
return (
<HTMLTable interactive className="pull-table">
<thead>
<tr>
<th>&nbsp;</th>
<th>Author</th>
<th>Status</th>
<th>Last Action</th>
Expand All @@ -31,9 +44,14 @@ export default function PullTable({data}: Props) {
</tr>
</thead>
<tbody>
{data.flatMap((pulls, idx) => (
pulls.pulls.map((pull, idx2) => (
{data.map((pullList, idx) => (
pullList.pulls.map((pull, idx2) => (
<tr key={`${idx}-${idx2}`}>
<td className="cursor-pointer" onClick={() => handleStar(pull.number)}>
{stars.has(pull.number)
? <Tooltip content="Unstar pull request"><Icon icon="star" color="#FBD065"/></Tooltip>
: <Tooltip content="Star pull request"><Icon icon="star-empty"/></Tooltip>}
</td>
<td>
<a href={pull.url}>
<div className="pull-author">
Expand Down Expand Up @@ -75,7 +93,7 @@ export default function PullTable({data}: Props) {
<a href={pull.url}>
<div className="font-semibold">{pull.title}</div>
<div className="text-sm">
{pulls.host}:{pull.repository.nameWithOwner} #{pull.number}
{pullList.host}:{pull.repository.nameWithOwner} #{pull.number}
</div>
</a>
</td>
Expand Down
1 change: 1 addition & 0 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default function Sidebar({ isDark, onDarkChange }: Props) {
<div className="sidebar">
<img src="/logo.svg" height="30" className="mt-2 mb-2"/>
<SidebarLink link="/" title="Dashboard" icon="dashboard"/>
<SidebarLink link="/stars" title="Stars" icon="bookmark"/>
<SidebarLink link="/settings" title="Settings" icon="cog"/>
<div className="bottom">
<SidebarButton
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createContext } from "react"
export type Config = {
sections: Section[],
connections: Connection[],
stars: number[],
}

export type Section = {
Expand Down Expand Up @@ -55,14 +56,15 @@ export const defaultConfig: Config = {
}
],
connections: [],
stars: [],
}

export const emptyConfig = {sections: [], connections: []}
export const emptySectionConfig = {label: "", search: "", notified: false}

export function readConfig(): Promise<Config> {
return localforage.getItem<Config>(configKey)
.then(config => (config === null) ? defaultConfig : config)
.then(config => (config === null) ? defaultConfig : {...defaultConfig, ...config})
}

export function writeConfig(config: Config): Promise<Config> {
Expand Down
38 changes: 21 additions & 17 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
createBrowserRouter,
RouterProvider,
} from 'react-router-dom'
import {createBrowserRouter, RouterProvider} from 'react-router-dom'
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import TimeAgo from 'javascript-time-ago'
import timeAgoEnLocale from 'javascript-time-ago/locale/en.json'
import { Intent } from '@blueprintjs/core'
import { Intent, BlueprintProvider } from '@blueprintjs/core'

import { AppToaster} from './toaster'
import App from './App.tsx'
Expand All @@ -20,6 +17,7 @@ import 'normalize.css/normalize.css'
import '@blueprintjs/icons/lib/css/blueprint-icons.css'
import '@blueprintjs/core/lib/css/blueprint.css'
import './styles/index.less'
import Stars from './routes/stars.tsx'

TimeAgo.addDefaultLocale(timeAgoEnLocale)

Expand All @@ -29,14 +27,18 @@ const router = createBrowserRouter([
element: <App/>,
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Dashboard/>,
},
{
path: "/settings",
element: <Settings/>,
},
{
index: true,
element: <Dashboard/>,
},
{
path: "/stars",
element: <Stars/>,
},
{
path: "/settings",
element: <Settings/>,
},
]
},
])
Expand All @@ -52,9 +54,11 @@ const queryClient = new QueryClient({

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools position="bottom-right"/>
<RouterProvider router={router} />
</QueryClientProvider>
<BlueprintProvider>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools position="bottom-right"/>
<RouterProvider router={router} />
</QueryClientProvider>
</BlueprintProvider>
</React.StrictMode>,
)
27 changes: 27 additions & 0 deletions src/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { UseQueryResult, useQueries } from "@tanstack/react-query";
import { Config } from "./config";
import { getPulls, getViewer } from "./github";
import { Pull } from "./model";

export const usePullRequests = (config: Config): UseQueryResult<Pull[]>[] => {
const viewers = useQueries({
queries: config.connections.map(connection => ({
queryKey: ['viewer', connection.host],
queryFn: () => getViewer(connection),
staleTime: Infinity,
})),
})
return useQueries({
queries: config.sections.flatMap(section => {
return config.connections.map((connection, idx) => ({
queryKey: ['pulls', connection.host, connection.auth, section.search],
queryFn: () => getPulls(connection, section.search, viewers[idx].data?.login || ""),
refetchInterval: 300_000,
refetchIntervalInBackground: true,
// refetchOnWindowFocus: false,
staleTime: 60_000,
enabled: viewers[idx].data !== undefined,
}))
}),
})
}
40 changes: 11 additions & 29 deletions src/routes/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useCallback, useContext, useEffect, useState } from "react";
import { Section, emptySectionConfig, ConfigContext } from "../config";
import DashboardSection from "../components/DashboardSection";
import { Button } from "@blueprintjs/core";
import SectionDialog from "../components/SectionDialog";
import { useQueries } from "@tanstack/react-query";
import { useSearchParams } from "react-router-dom";
import { getPulls, getViewer } from "../github";
import { Pull } from "../model";
import SearchInput from "../components/SearchInput";
import { useCallback, useContext, useEffect, useState } from "react"
import { useSearchParams } from "react-router-dom"
import { Button } from "@blueprintjs/core"

import { Section, emptySectionConfig, ConfigContext } from "../config"
import SectionDialog from "../components/SectionDialog"
import DashboardSection from "../components/DashboardSection"
import { Pull } from "../model"
import SearchInput from "../components/SearchInput"
import { usePullRequests } from "../queries"

function matches(pull: Pull, tokens: string[]): boolean {
return tokens.length === 0 || tokens.every(tok => pull.title.toLowerCase().indexOf(tok) > -1 || pull.repository.nameWithOwner.indexOf(tok) > -1)
Expand All @@ -24,25 +24,7 @@ export default function Dashboard() {
const [ searchParams, setSearchParams ] = useSearchParams()
const [ newSection, setNewSection ] = useState(emptySectionConfig)

const viewers = useQueries({
queries: config.connections.map(connection => ({
queryKey: ['viewer', connection.host],
queryFn: () => getViewer(connection),
staleTime: Infinity,
})),
})
const results = useQueries({
queries: config.sections.flatMap(section => {
return config.connections.map((connection, idx) => ({
queryKey: ['pulls', connection.host, connection.auth, section.search],
queryFn: () => getPulls(connection, section.search, viewers[idx].data?.login || ""),
refetchInterval: 300_000,
refetchIntervalInBackground: true,
refetchOnWindowFocus: false,
enabled: viewers[idx].data !== undefined,
}))
}),
})
const results = usePullRequests(config)

const refetchAll = useCallback(async () => {
await Promise.all(results.map(res => res.refetch()));
Expand Down
49 changes: 49 additions & 0 deletions src/routes/stars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useCallback, useContext } from "react";
import { Button, Card, H3, Spinner } from "@blueprintjs/core";

import { ConfigContext } from "../config";
import PullTable from "../components/PullTable";
import { usePullRequests } from "../queries";


export default function Stars() {
const { config } = useContext(ConfigContext)
const results = usePullRequests(config)

const stars = new Set(config.stars)

const isLoading = results.some(res => res.isLoading)
const isFetching = results.some(res => res.isFetching)

const data = config.connections.map((connection, idx) => ({
host: connection.host,
pulls: config.sections.flatMap((_, idx2) => results[idx + config.connections.length * idx2].data || []).filter(v => stars.has(v.number))
}))
const count = data.map(res => res.pulls.length).reduce((acc, v) => acc + v, 0)

const refetchAll = useCallback(async () => {
await Promise.all(results.map(res => res.refetch()));
}, [results]);

return (
<>
<div className="flex mb-4">
<H3 className="grow">Starred pull requests</H3>
<Button
icon="refresh"
disabled={isFetching}
loading={isFetching}
className="ml-4"
onClick={refetchAll}/>
</div>

<Card className="mt-4">
{isLoading
? <Spinner/>
: count > 0
? <PullTable data={data}/>
: <p className="no-results">No results</p>}
</Card>
</>
)
}
12 changes: 8 additions & 4 deletions src/styles/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,22 @@ footer {
overflow: hidden;
vertical-align: middle;
}
tbody td:nth-child(1) { /* Author */
tbody td:nth-child(1) { /* Star */
text-align: center;
width: 25px;
}
tbody td:nth-child(2) { /* Author */
text-align: center;
width: 50px;
}
tbody td:nth-child(2) { /* Status */
tbody td:nth-child(3) { /* Status */
text-align: center;
width: 50px;
}
tbody td:nth-child(3) { /* Last Action */
tbody td:nth-child(4) { /* Last Action */
width: 200px;
}
tbody td:nth-child(4) { /* Size */
tbody td:nth-child(5) { /* Size */
text-align: center;
width: 50px;
}
Expand Down
4 changes: 4 additions & 0 deletions src/styles/utils.less
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
flex-grow: 1;
}

.cursor-pointer {
cursor: pointer;
}

.text-sm {
font-size: @pt-font-size-small;
}
Expand Down

0 comments on commit 0e4ee1a

Please sign in to comment.