Skip to content

Commit

Permalink
Abstract pull request as a 'diff'
Browse files Browse the repository at this point in the history
  • Loading branch information
pvcnt committed May 21, 2024
1 parent f3210b7 commit 602e7a8
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 107 deletions.
6 changes: 3 additions & 3 deletions src/components/ConnectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { User } from "../model"

type Props = {
connection: Connection,
viewer?: User,
user?: User,
onDelete: () => void,
}

const isBlank = (value: string | null | undefined) => value == null || value === ''

export default function ConnectionCard({connection, viewer, onDelete}: Props) {
export default function ConnectionCard({connection, user, onDelete}: Props) {
const [isDeleting, setDeleting] = useState(false)
return (
<>
Expand All @@ -22,7 +22,7 @@ export default function ConnectionCard({connection, viewer, onDelete}: Props) {
<H4>{connection.name}</H4>
)}
<span><b>Host:</b> <a href={`https://${connection.host}`}>{connection.host}</a></span>
{viewer !== undefined && <span className="ml-4"><b>User: </b>{viewer.login}</span>}
{user !== undefined && <span className="ml-4"><b>User: </b>{user.name}</span>}
</div>
<Button text="Delete" minimal intent={Intent.DANGER} onClick={() => setDeleting(true)}></Button>
</Card>
Expand Down
16 changes: 7 additions & 9 deletions src/components/DashboardSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ import { Button, Card, Collapse, H5, Icon, Intent, Spinner, Tag } from "@bluepri
import { useState } from "react"
import SectionDialog from "./SectionDialog";
import { Section } from "../config";
import PullTable from "./PullTable";
import { PullList } from "../model";
import DiffTable from "./DiffTable";
import { Diff } from "../model";

export type Props = {
section: Section,
isFirst: boolean,
isLast: boolean,
isLoading: boolean,
data: PullList[],
diffs: Diff[],
onMoveUp: () => void,
onMoveDown: () => void,
onChange: (config: Section) => void,
onDelete: () => void,
}

export default function DashboardSection({isLoading, section, isFirst, isLast, data, onChange, onMoveUp, onMoveDown, onDelete}: Props) {
export default function DashboardSection({isLoading, section, isFirst, isLast, diffs, onChange, onMoveUp, onMoveDown, onDelete}: Props) {
const [isCollapsed, setCollapsed] = useState(false)
const [isEditing, setEditing] = useState(false)

const count = data.map(res => res.pulls.length).reduce((acc, v) => acc + v, 0)

const handleTitleClick = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault()
setCollapsed(v => !v)
Expand All @@ -35,8 +33,8 @@ 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>
{(count > 0) && (
<Tag round intent={Intent.NONE} className="ml-2">{count}</Tag>
{(diffs.length > 0) && (
<Tag round intent={Intent.NONE} className="ml-2">{diffs.length}</Tag>
)}
</div>
</H5>
Expand All @@ -59,7 +57,7 @@ export default function DashboardSection({isLoading, section, isFirst, isLast, d
{isLoading
? <Spinner/>
: <Collapse isOpen={!isCollapsed}>
{count > 0 ? <PullTable data={data} /> : <p className="no-results">No results</p>}
{diffs.length > 0 ? <DiffTable diffs={diffs} /> : <p className="no-results">No results</p>}
</Collapse>}
</Card>
)
Expand Down
73 changes: 36 additions & 37 deletions src/components/PullTable.tsx → src/components/DiffTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { useContext } from "react"
import { HTMLTable, Tooltip, Tag, Icon } from "@blueprintjs/core"
import ReactTimeAgo from "react-time-ago"

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


export type Props = {
data: PullList[],
diffs: Diff[],
}

const formatDate = (d: string) => {
Expand All @@ -21,18 +21,18 @@ const formatDate = (d: string) => {
})
}

export default function PullTable({data}: Props) {
export default function DiffTable({diffs}: 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])})
const handleStar = (uid: string) => {
setConfig(prev => prev.stars.indexOf(uid) > -1
? {...prev, stars: prev.stars.filter(s => s != uid)}
: {...prev, stars: prev.stars.concat([uid])})
}
return (
<HTMLTable interactive className="pull-table">
<HTMLTable interactive className="diff-table">
<thead>
<tr>
<th>&nbsp;</th>
Expand All @@ -44,62 +44,61 @@ export default function PullTable({data}: Props) {
</tr>
</thead>
<tbody>
{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>}
{diffs.map((diff, idx) => (
<tr key={idx}>
<td className="cursor-pointer" onClick={() => handleStar(diff.uid)}>
{stars.has(diff.uid)
? <Tooltip content="Unstar diff"><Icon icon="star" color="#FBD065"/></Tooltip>
: <Tooltip content="Star diff"><Icon icon="star-empty"/></Tooltip>}
</td>
<td>
<a href={pull.url}>
<div className="pull-author">
<Tooltip content={pull.author.login}>
{pull.author.avatarUrl ? <img src={pull.author.avatarUrl}/> : <Icon icon="user"/>}
<a href={diff.url}>
<div className="diff-author">
<Tooltip content={diff.author.name}>
{diff.author.avatarUrl ? <img src={diff.author.avatarUrl}/> : <Icon icon="user"/>}
</Tooltip>
</div>
</a>
</td>
<td>
<a href={pull.url}>
{pull.isDraft
<a href={diff.url}>
{diff.state == DiffState.Draft
? <IconWithTooltip icon="document" title="Draft" color="#5F6B7C"/>
: pull.merged
: diff.state == DiffState.Merged
? <IconWithTooltip icon="git-merge" title="Merged" color="#634DBF"/>
: pull.closed
: diff.state == DiffState.Closed
? <IconWithTooltip icon="cross-circle" title="Closed" color="#AC2F33"/>
: pull.reviewDecision == "APPROVED"
: diff.state == DiffState.Approved
? <IconWithTooltip icon="git-pull" title="Approved" color="#1C6E42"/>
: pull.reviewDecision == "CHANGES_REQUESTED"
: diff.state == DiffState.ChangesRequested
? <IconWithTooltip icon="issue" title="Changes requested" color="#C87619"/>
: <IconWithTooltip icon="git-pull" title="Pending review" color="#C87619"/>
}
: diff.state == DiffState.Pending
? <IconWithTooltip icon="git-pull" title="Pending review" color="#C87619"/>
: null}
</a>
</td>
<td>
<a href={pull.url}>
<Tooltip content={formatDate(pull.updatedAt)}>
<ReactTimeAgo date={new Date(pull.updatedAt)} tooltip={false} timeStyle="round"/>
<a href={diff.url}>
<Tooltip content={formatDate(diff.updatedAt)}>
<ReactTimeAgo date={new Date(diff.updatedAt)} tooltip={false} timeStyle="round"/>
</Tooltip>
</a>
</td>
<td>
<a href={pull.url}>
<Tooltip content={<><span className="additions">+{pull.additions}</span> / <span className="deletions">-{pull.deletions}</span></>} openOnTargetFocus={false} usePortal={false}><Tag>{computeSize(pull)}</Tag></Tooltip>
<a href={diff.url}>
<Tooltip content={<><span className="additions">+{diff.additions}</span> / <span className="deletions">-{diff.deletions}</span></>} openOnTargetFocus={false} usePortal={false}><Tag>{computeSize(diff)}</Tag></Tooltip>
</a>
</td>
<td>
<a href={pull.url}>
<div className="font-semibold">{pull.title}</div>
<a href={diff.url}>
<div className="font-semibold">{diff.title}</div>
<div className="text-sm">
{pullList.host}:{pull.repository.nameWithOwner} #{pull.number}
{diff.host}:{diff.repository} #{diff.id}
</div>
</a>
</td>
</tr>
))
))}
))}
</tbody>
</HTMLTable>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function SearchInput({value, onChange, className}: Props) {
<HotkeysTarget2 hotkeys={hotkeys}>
<InputGroup
leftIcon="search"
placeholder="Search pull requests"
placeholder="Search diffs"
round
className={className}
value={value}
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createContext } from "react"
export type Config = {
sections: Section[],
connections: Connection[],
stars: number[],
stars: string[],
}

export type Section = {
Expand Down
65 changes: 61 additions & 4 deletions src/github.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import { Octokit } from "@octokit/rest"
import { Connection } from "./config"
import { Pull, User } from "./model"
import { Diff, DiffState, User } from "./model"

export function createClient(connection: Connection): Octokit {
return new Octokit({auth: connection.auth, baseUrl: connection.baseUrl})
}

type Pull = {
number: number,
title: string,
state: string,
author: GitHubUser,
createdAt: string,
updatedAt: string,
url: string,
additions: number,
deletions: number,
repository: {
nameWithOwner: string,
},
isDraft: boolean,
merged: boolean,
closed: boolean,
reviewDecision?: string,
}

type GitHubUser = {
name: string,
login: string,
avatarUrl: string,
}

type RateLimit = {
limit: number,
cost: number,
Expand All @@ -16,7 +41,6 @@ type RateLimit = {
export function getViewer(connection: Connection): Promise<User> {
const query = `query {
viewer {
name
login
avatarUrl
}
Expand All @@ -28,13 +52,46 @@ export function getViewer(connection: Connection): Promise<User> {
}
}`
type Data = {
viewer: User,
viewer: GitHubUser,
rateLimit: RateLimit,
}

return createClient(connection).graphql<Data>(query)
.then(data => data.viewer)
}
.then(user => ({name: user.login, avatarUrl: user.avatarUrl}))
}

export function getDiffs(connection: Connection, search: string, user: string): Promise<Diff[]> {
return getPulls(connection, search, user).then(pulls => {
return pulls.map(pull => ({
uid: `${connection.host}/${pull.number}`,
host: connection.host,
id: `${pull.number}`,
repository: pull.repository.nameWithOwner,
title: pull.title,
state: pull.isDraft
? DiffState.Draft
: pull.merged
? DiffState.Merged
: pull.closed
? DiffState.Closed
: pull.reviewDecision == "APPROVED"
? DiffState.Approved
: pull.reviewDecision == "CHANGES_REQUESTED"
? DiffState.ChangesRequested
: DiffState.Pending,
createdAt: pull.createdAt,
updatedAt: pull.updatedAt,
url: pull.url,
additions: pull.additions,
deletions: pull.deletions,
author: {
name: pull.author.login,
avatarUrl: pull.author.avatarUrl,
},
}))
})
}

export function getPulls(connection: Connection, search: string, user: string): Promise<Pull[]> {
const query = `query dashboard($search: String!) {
Expand Down
37 changes: 18 additions & 19 deletions src/model.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
export type Pull = {
number: number,
export enum DiffState {
Draft = 1,
Pending,
Approved,
ChangesRequested,
Merged,
Closed,
}

export type Diff = {
uid: string,
host: string,
id: string,
repository: string,
title: string,
state: string,
author: User,
state: DiffState,
createdAt: string,
updatedAt: string,
url: string,
additions: number,
deletions: number,
repository: {
nameWithOwner: string,
},
isDraft: boolean,
merged: boolean,
closed: boolean,
reviewDecision?: string,
}

export type PullList = {
host: string,
pulls: Pull[],
author: User,
}

export type User = {
name: string,
login: string,
avatarUrl: string,
}

Expand All @@ -39,8 +38,8 @@ const sizes: {label: string, changes: number}[] = [
]
sizes.reverse()

export function computeSize(pull: Pull): string {
const changes = pull.additions + pull.deletions
export function computeSize(diff: Diff): string {
const changes = diff.additions + diff.deletions
const size = sizes.find(s => changes >= s.changes)
return size === undefined ? sizes[sizes.length - 1].label : size.label
}
Loading

0 comments on commit 602e7a8

Please sign in to comment.