Skip to content

Instantly share code, notes, and snippets.

@darcien
Last active May 31, 2023 16:39
Show Gist options
  • Save darcien/b393c7939e9ea14f8f428e089feb66bf to your computer and use it in GitHub Desktop.
Save darcien/b393c7939e9ea14f8f428e089feb66bf to your computer and use it in GitHub Desktop.
script to visualize recent pr line diff counts for internal jokes
#!/usr/bin/env -S deno run
import { sortBy } from "https://deno.land/std@0.186.0/collections/sort_by.ts";
import { groupBy } from "https://deno.land/std@0.186.0/collections/group_by.ts";
import { reduceGroups } from "https://deno.land/std@0.186.0/collections/reduce_groups.ts";
import { plot } from "https://deno.land/x/chart@1.1/mod.ts";
// Using JSON from this gh --json output
// gh pr list --json title,additions,deletions,changedFiles,author,number --search "uiv2 in:title" --state=all --limit 300
type Pr = {
additions: number;
author: {
id: string;
is_bot: boolean;
login: string;
name: string;
};
changedFiles: number;
deletions: number;
number: number;
title: string;
};
// Accepting JSON input from stdin
const decoder = new TextDecoder();
const chunks = [];
for await (const chunk of Deno.stdin.readable) {
chunks.push(decoder.decode(chunk));
}
const json = JSON.parse(chunks.join(""));
const rGhPrs = json as Array<Pr>;
const ghPrs = rGhPrs.filter((pr) => !pr.author.is_bot);
// This might need a patched nerdfonts
const folderIcon = "";
function formatPosition(position: number) {
let medal;
switch (position) {
case 1:
medal = "🥇";
break;
case 2:
medal = "🥈";
break;
case 3:
medal = "🥉";
break;
}
return medal;
}
function formatPr(pr: Pr, position: number) {
return [
`${formatPosition(position)} ${pr.author.name}`,
`${pr.title} #${pr.number}`,
`%c+${pr.additions} %c-${pr.deletions} %c${folderIcon}${pr.changedFiles}`,
].join("\n");
}
type AuthorMeta = {
totalAdditions: number;
totalDeletions: number;
totalAddDelete: number;
totalChangedFiles: number;
totalPrs: number;
author: Pr["author"];
};
function formatMeta(
meta: AuthorMeta,
position: number,
{
showAdditions,
showChangedFiles,
showPrCount,
showDeletions,
}: Partial<{
showAdditions: boolean;
showDeletions: boolean;
showPrCount: boolean;
showChangedFiles: boolean;
}> = {}
) {
const add = showAdditions ? `+${meta.totalAdditions}` : "";
const del = showDeletions ? `-${meta.totalDeletions}` : "";
const cha = showChangedFiles ? `${folderIcon}${meta.totalChangedFiles}` : "";
return [
`${formatPosition(position)} ${meta.author.name}`,
showPrCount ? `PR count: ${meta.totalPrs}` : null,
`%c${add} %c${del} %c${cha}`,
]
.filter(Boolean)
.join("\n");
}
const prsByAuthor = groupBy(ghPrs, (pr) => pr.author.id) as Record<
string,
Array<Pr>
>;
const metaByAuthor = reduceGroups(
prsByAuthor,
(meta, pr) => ({
totalAdditions: meta.totalAdditions + pr.additions,
totalDeletions: meta.totalDeletions + pr.deletions,
totalAddDelete: meta.totalAddDelete + pr.additions + pr.deletions,
totalChangedFiles: meta.totalChangedFiles + pr.changedFiles,
totalPrs: meta.totalPrs + 1,
author: pr.author,
}),
{
totalAdditions: 0,
totalDeletions: 0,
totalAddDelete: 0,
totalChangedFiles: 0,
totalPrs: 0,
author: {},
} as AuthorMeta
);
const metas = Object.values(metaByAuthor);
const [
authorAdditions,
authorDeletions,
authorAddDelete,
authorTotalPrs,
authorChangedFiles,
] = [
sortBy(metas, (meta) => meta.totalAdditions),
sortBy(metas, (meta) => meta.totalDeletions),
sortBy(metas, (meta) => meta.totalAddDelete),
sortBy(metas, (meta) => meta.totalPrs),
sortBy(metas, (meta) => meta.totalChangedFiles),
];
const [prAdditions, prDeletions, prAddDelete, prChangedFiles] = [
sortBy(ghPrs, (pr) => pr.additions),
sortBy(ghPrs, (pr) => pr.deletions),
sortBy(ghPrs, (pr) => pr.additions + pr.deletions),
sortBy(ghPrs, (pr) => pr.changedFiles),
];
const [top3Additions, top3Deletions, top3AddDelete, top3ChangedFiles] = [
prAdditions.slice(-3).reverse(),
prDeletions.slice(-3).reverse(),
prAddDelete.slice(-3).reverse(),
prChangedFiles.slice(-3).reverse(),
];
console.log("\n###\n");
console.log("ADDITIONS AND DELETIONS PLOT\n");
console.log(
plot(
[
prAdditions.map((pr) => pr.additions),
prDeletions.map((pr) => pr.deletions),
],
{
colors: ["green", "red"],
height: 24,
}
)
);
console.log("\n###\n");
console.log("CHANGED FILES PLOT\n");
console.log(
plot([prChangedFiles.map((pr) => pr.changedFiles)], {
colors: ["magenta"],
height: 24,
})
);
function formatPrRankingAndLog(arr: typeof ghPrs) {
arr
.map((pr, index) => formatPr(pr, index + 1))
.map((formatted) =>
console.log(
formatted + "\n",
"color: green",
"color: red",
"color: yellow"
)
);
}
function formatAuthorRankingAndLog(
arr: typeof metas,
config: Partial<{
showAdditions: boolean;
showDeletions: boolean;
showPrCount: boolean;
showChangedFiles: boolean;
}>
) {
arr
.map((meta, index) => formatMeta(meta, index + 1, config))
.map((formatted) =>
console.log(
formatted + "\n",
"color: green",
"color: red",
"color: yellow"
)
);
}
console.log("\n###\n");
console.log("TOP 3 ADDITIONS BY PR!\n");
formatPrRankingAndLog(top3Additions);
console.log("\n###\n");
console.log("TOP 3 DELETIONS BY PR!\n");
formatPrRankingAndLog(top3Deletions);
console.log("\n###\n");
console.log("TOP 3 ADDITIONS + DELETIONS BY PR!\n");
formatPrRankingAndLog(top3AddDelete);
console.log("\n###\n");
console.log("TOP 3 CHANGED FILES BY PR!\n");
formatPrRankingAndLog(top3ChangedFiles);
console.log("\n###\n");
console.log("TOP 3 TOTAL ADDITIONS!\n");
formatAuthorRankingAndLog(authorAdditions.slice(-3).reverse(), {
showAdditions: true,
});
console.log("\n###\n");
console.log("TOP 3 TOTAL DELETIONS!\n");
formatAuthorRankingAndLog(authorDeletions.slice(-3).reverse(), {
showDeletions: true,
});
console.log("\n###\n");
console.log("TOP 3 TOTAL ADDITIONS + DELETIONS!\n");
formatAuthorRankingAndLog(authorAddDelete.slice(-3).reverse(), {
showAdditions: true,
showDeletions: true,
});
console.log("\n###\n");
console.log("TOP 3 TOTAL PRS!\n");
formatAuthorRankingAndLog(authorTotalPrs.slice(-3).reverse(), {
showPrCount: true,
});
console.log("\n###\n");
console.log("TOP 3 TOTAL CHANGED FILES!\n");
formatAuthorRankingAndLog(authorChangedFiles.slice(-3).reverse(), {
showChangedFiles: true,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment