Jakub Olek

Jakub Olek

// Menu: Currency Converter using google
// Author: Jakub Olek
// Twitter: @JakubOlek
/** @type {import("@johnlindquist/kit")} */
const from = await arg("from", ["pln", "usd", "eur"]);
const to = await arg("to", ["usd", "pln", "eur"]);
const query = await arg("amount");
const value = await scrapeSelector(
`https://google.com/search?q=${encodeURIComponent(
query + " " + from + " " + to
)}`,
"span[data-value]"
);
div(query + " " + from + " = " + value + " " + to, "p-4");
// Menu: RCKIK mobile
// Description: Show filtered plan of mobile RCKIK busses
// Author: Jakub Olek
// Twitter: @JakubOlek
/** @type {import("@johnlindquist/kit")} */
function transform(node) {
// Edit that to filter locations that you're interested in
// has to be inside transform function - as it's being serialized and passed to browser
const filter = "Poznań";
const columns = [...node.querySelectorAll("td")];
if (columns[3].innerHTML.startsWith(filter)) {
return (
columns[0].querySelector("a").innerHTML +
" " +
columns[3].innerHTML +
" " +
columns[2].innerHTML
);
}
}
const pagination = await scrapeSelector(
"https://www.rckik.poznan.pl/najblizsze-wyjazdy?page=1",
".pagination li"
);
const numberOfPages = pagination.length;
let t = [];
for (let i = 1; i <= numberOfPages; i += 1) {
const result = await scrapeSelector(
`https://www.rckik.poznan.pl/najblizsze-wyjazdy?page=${i}`,
"#calendarTable tr:not(.header):not(.canceled)",
transform
);
t = t.concat(result);
}
div(
`<ul>${t
.filter(Boolean)
.map((date) => `<li>${date}</li>`)
.join("")}</ul>`,
"p-4"
);
// Menu: Ping
// Description: Ping destination and show line graph of latest values
// Author: Jakub Olek
// Twitter: @JakubOlek
const jsdom = await npm("jsdom");
await npm("canvas");
const Chart = await npm("chart.js");
const { JSDOM } = jsdom;
// Edit the list to suit your needs
const destination = await arg("ping", ["8.8.8.8", "google.com"]);
// How many entries should the chart show
const entries = 20;
const command = `ping ${destination}`;
const child = exec(command, { async: true });
const dom = new JSDOM(
`<!DOCTYPE html><canvas id="bar-chart" width="800" height="450"></canvas>`
);
global.window = dom.window;
Chart.defaults.color = "white";
Chart.defaults.font = { size: 24, weight: "bold" };
const labels = new Array(entries)
.fill()
.map((_, i) => i)
.reverse();
let output = [];
const chartData = {
labels: labels,
datasets: [
{
label: `${command}: - ms`,
backgroundColor: "rgb(255, 99, 132)",
borderColor: "rgb(255, 99, 132)",
data: output,
},
],
};
const chart = new Chart(dom.window.document.getElementById("bar-chart"), {
type: "line",
data: chartData,
options: {
animation: false,
tooltips: { enabled: false },
hover: { mode: null },
},
});
let firstLine = true;
child.stdout.on("data", function (data) {
if (!firstLine) {
const value = +data.replace(/.*time=(.*)ms/, "$1");
output.push(value);
if (output.length > entries) {
output = output.slice(1);
}
chartData.datasets[0].label = `${command}: ${value}ms`;
chartData.datasets[0].data = output;
chart.update();
div(`<img src="${chart.toBase64Image("image/jpeg", 1)}"/>`, "p-4");
} else {
firstLine = false;
}
});
// Menu: GitLab - next MR
// Description: Open next MR that I have not approved
// Author: Jakub Olek
// Twitter: @JakubOlek
// Shortcut: ctrl opt \
const { request, gql, GraphQLClient } = await npm("graphql-request");
const dayjs = await npm("dayjs");
import relativeTime from "dayjs/plugin/relativeTime.js";
dayjs.extend(relativeTime);
const domain = await env("GITLAB_DOMAIN");
const token = await env("GITLAB_TOKEN");
const username = await env("GITLAB_USERNAME");
const jiraDomain = await env("JIRA_DOMAIN");
const requiredApprovals = Number(await env("GITLAB_REQUIRED_APPROVALS"));
const debug = false;
function log(...args) {
if (debug) {
console.log(...args);
}
}
const graphQLClient = new GraphQLClient(domain + "/api/graphql", {
headers: {
"PRIVATE-TOKEN": token,
},
});
const projects = gql`
query($name: String!) {
projects(search: $name, membership: true) {
nodes {
nameWithNamespace
fullPath
}
}
}
`;
if (!env.GITLAB_PROJECT_PATH) {
const fullPath = await arg("Search project", async (input) => {
return (
await graphQLClient.request(projects, { name: input })
).projects.nodes.map((project) => ({
name: project.nameWithNamespace,
description: project.fullPath,
value: project.fullPath,
}));
});
await cli("set-env-var", "GITLAB_PROJECT_PATH", fullPath);
}
const queryMrs = gql`
query($projectPath: ID!) {
project(fullPath: $projectPath) {
mergeRequests(state: opened, sort: UPDATED_DESC) {
nodes {
title
webUrl
iid
draft
description
createdAt
approvedBy {
nodes {
name
username
}
}
author {
name
username
avatarUrl
}
}
}
}
}
`;
const query = gql`
query($iid: String!, $projectPath: ID!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
commitsWithoutMergeCommits(first: 1) {
nodes {
authoredDate
}
}
headPipeline {
status
}
notes {
nodes {
updatedAt
author {
username
}
}
}
}
}
}
`;
let nextMR;
const myMrs = [];
const drafts = [];
const awaitingReview = [];
const alreadyCommented = [];
const haveAuthorCommented = [];
const haveOthersCommented = [];
const haveFailingPipeline = [];
const alreadyApprovedByMe = [];
const alreadyApprovedByOthers = [];
const {
project: {
mergeRequests: { nodes: mergeRequests },
},
} = await graphQLClient.request(queryMrs, {
projectPath: env.GITLAB_PROJECT_PATH,
});
arg("Processing...");
log("Show list", flag.showList);
log("Checking", mergeRequests.length, "MRs");
for (let mr of mergeRequests) {
log("Checking MR", mr.title, `(${mr.author.username})`);
const approvedBy = mr.approvedBy.nodes.map((node) => node.username);
if (mr.author.username === username) {
log("^ This is my MR");
myMrs.push(mr);
continue;
}
if (mr.draft) {
drafts.push(mr);
log("^ This is a draft");
continue;
}
if (approvedBy.includes(username)) {
log("^ Approved by me");
alreadyApprovedByMe.push(mr);
continue;
} else {
if (approvedBy.length >= requiredApprovals) {
log("^ Approved by others");
alreadyApprovedByOthers.push(mr);
continue;
}
const {
project: { mergeRequest },
} = await graphQLClient.request(query, {
iid: mr.iid,
projectPath: env.GITLAB_PROJECT_PATH,
});
const pipelineStatus = mergeRequest.headPipeline.status;
if (pipelineStatus !== "SUCCESS") {
log("^ Failed pipeline");
haveFailingPipeline.push(mr);
continue;
}
const comments = mergeRequest.notes.nodes;
const anyLatestComment = comments[0];
const myLatestComment = comments.find(
(comment) => comment.author.username === username
);
const authorLatestComment = comments.find(
(comment) => comment.author.username === mr.author.username
);
if (myLatestComment) {
const latestCommitTime = dayjs(
mergeRequest.commitsWithoutMergeCommits.nodes[0].authoredDate
);
const myLatestCommentTime = dayjs(myLatestComment.updatedAt);
if (latestCommitTime.isBefore(myLatestCommentTime)) {
log("^ awaits new commits after my comments");
alreadyCommented.push(mr);
continue;
}
if (authorLatestComment) {
const authorLatestCommentTime = dayjs(authorLatestComment.updatedAt);
if (authorLatestCommentTime.isAfter(myLatestComment.updatedAt)) {
log("^ have some comments by the MR author after my comment");
haveAuthorCommented.push(mr);
continue;
}
}
if (anyLatestComment) {
const latestCommentTime = dayjs(anyLatestComment.updatedAt);
if (latestCommentTime.isAfter(myLatestComment.updatedAt)) {
log("^ have some comments by other after my comment");
haveOthersCommented.push(mr);
continue;
}
}
}
if (!flag.showList) {
nextMR = mr;
break;
} else {
awaitingReview.push(mr);
}
}
}
function createJiraLinks(text) {
return text.replace(
/[A-Z]{1,5}-[0-9]*/g,
(ticketNumber) => `[${ticketNumber}](${jiraDomain}}/browse/${ticketNumber})`
);
}
function getName(mr) {
if (mr.author.username === username) {
return `${!mr.draft && mr.approvedBy.nodes.length < 2 ? "!A " : ""}${
mr.title
}`;
}
return mr.title;
}
function getChoices(mrs, description) {
return mrs.map((mr) => ({
name: getName(mr),
value: mr.webUrl,
description: description,
img: mr.author.avatarUrl.includes("http")
? mr.author.avatarUrl
: domain + mr.author.avatarUrl,
preview: md(
`# ${createJiraLinks(mr.title)}
## Created ${dayjs(mr.createdAt).fromNow()} by ${mr.author.name}
## ${description}
## Approved by
${
mr.approvedBy.nodes.length
? mr.approvedBy.nodes
.map(
(user) => `* ${user.name}
`
)
.join("")
: "- nobody"
}
${createJiraLinks(
mr.description.replace(
/\/uploads\//g,
domain + "/uploads/" + env.GITLAB_PROJECT_PATH + "/"
)
)}`
),
}));
}
if (nextMR) {
await focusTab(nextMR.webUrl);
} else {
const choices = [
...getChoices(awaitingReview, "Awaiting Review"),
...getChoices(haveAuthorCommented, "Author have comments after you"),
...getChoices(haveOthersCommented, "Someone have comments after you"),
...getChoices(myMrs, "My merge request"),
...getChoices(haveFailingPipeline, "Failing Pipeline"),
...getChoices(alreadyCommented, "You have commented on this"),
...getChoices(alreadyApprovedByOthers, "Already approved by others"),
...getChoices(alreadyApprovedByMe, "Already approved by you"),
...getChoices(drafts, "Draft"),
];
if (choices.length) {
const mr = await arg("Open MR:", choices);
if (mr) {
focusTab(mr);
}
}
}
// Menu: Conventional comment
// Description: Comments that are easy to grok and grep
// Author: Jakub Olek
// Twitter: @JakubOlek
// Shortcut: opt 0
// Based on: https://hemdan.hashnode.dev/conventional-comments
const type = await arg("Label", [
{
name: "👏 praise",
value: "**👏 praise**: ",
description:
"Praises highlight something positive. Try to leave at least one of these comments per review (if it exists :^)",
},
{
name: "🤓 nitpick",
value: "**🤓 nitpick**: ",
description:
"Nitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement.",
},
{
name: "🎯 suggestion",
value: "**🎯 suggestion**: ",
description:
"Suggestions are specific requests to improve the subject under review. It is assumed that we all want to do what's best, so these comments are never dismissed as “mere suggestions”, but are taken seriously.",
},
{
name: "🔨 issue",
value: "**🔨 issue**: ",
description:
"Issues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion.",
},
{
name: "❔ question",
value: "**❔ question**: ",
description:
"Questions are appropriate if you have a potential concern but are not quite sure if it's relevant or not. Asking the author for clarification or investigation can lead to a quick resolution.",
},
{
name: "💭 thought",
value: "**💭 thought**: ",
description:
"Thoughts represent an idea that popped up from reviewing. These comments are non-blocking by nature, but they are extremely valuable and can lead to more focused initiatives and mentoring opportunities.",
},
{
name: "💣 chore",
value: "**💣 chore**: ",
description:
"Chores are simple tasks that must be done before the subject can be “officially” accepted. Usually, these comments reference some common processes. Try to leave a link to the process described so that the reader knows how to resolve the chore.",
},
]);
setSelectedText(type);