init
This commit is contained in:
commit
f159fab9a9
6 changed files with 195 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/okimeter
|
||||||
|
/token
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"deno.enable": true
|
||||||
|
}
|
9
build.sh
Executable file
9
build.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$(realpath "$0")")"
|
||||||
|
|
||||||
|
MAIN="main.ts"
|
||||||
|
ARGS="--allow-net"
|
||||||
|
BIN="okimeter"
|
||||||
|
|
||||||
|
deno compile "$ARGS" -o "$BIN" "$MAIN"
|
6
deno.json
Normal file
6
deno.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"fmt": {
|
||||||
|
"useTabs": true,
|
||||||
|
"lineWidth": 120
|
||||||
|
}
|
||||||
|
}
|
37
deno.lock
generated
Normal file
37
deno.lock
generated
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"version": "3",
|
||||||
|
"redirects": {
|
||||||
|
"https://deno.land/x/crayon/mod.ts": "https://deno.land/x/crayon@3.3.3/mod.ts",
|
||||||
|
"https://deno.land/x/crayon/src/styles.ts": "https://deno.land/x/crayon@3.3.3/src/styles.ts"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"https://deno.land/std@0.222.1/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
|
||||||
|
"https://deno.land/std@0.222.1/assert/_diff.ts": "4bf42969aa8b1a33aaf23eb8e478b011bfaa31b82d85d2ff4b5c4662d8780d2b",
|
||||||
|
"https://deno.land/std@0.222.1/assert/_format.ts": "0ba808961bf678437fb486b56405b6fefad2cf87b5809667c781ddee8c32aff4",
|
||||||
|
"https://deno.land/std@0.222.1/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
|
||||||
|
"https://deno.land/std@0.222.1/assert/assert_equals.ts": "cc1f4b0ff4ad511e69f965535b56a6cdbbbc0f086bf376e0243214df6039c883",
|
||||||
|
"https://deno.land/std@0.222.1/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
|
||||||
|
"https://deno.land/std@0.222.1/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
|
||||||
|
"https://deno.land/std@0.222.1/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376",
|
||||||
|
"https://deno.land/std@0.222.1/encoding/base64.ts": "dd59695391584c8ffc5a296ba82bcdba6dd8a84d41a6a539fbee8e5075286eaf",
|
||||||
|
"https://deno.land/std@0.222.1/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a",
|
||||||
|
"https://deno.land/x/crayon@3.3.3/mod.ts": "82ad225583a483c4837577971629cddaa22614093af8353da6426b9366de9780",
|
||||||
|
"https://deno.land/x/crayon@3.3.3/src/conversions.ts": "9bfd3b1fbe412bcba092890ac558b6beaad4c3aa399cd99d45fadb324d28afd6",
|
||||||
|
"https://deno.land/x/crayon@3.3.3/src/crayon.ts": "6b237baa08a31c903436e040afd2228a7fffaa5d11dddc58e3c402f79b3c1d04",
|
||||||
|
"https://deno.land/x/crayon@3.3.3/src/styles.ts": "aa588b57b2c0482dc5c6f53109b4287831a9827c0aeef9a88129beae1172c1ee",
|
||||||
|
"https://deno.land/x/crayon@3.3.3/src/util.ts": "af8884a917488de76ac0c2b92482093ade74514ece77a4c64e5eb5b0f6ed68e6",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4",
|
||||||
|
"https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e"
|
||||||
|
}
|
||||||
|
}
|
138
main.ts
Executable file
138
main.ts
Executable file
|
@ -0,0 +1,138 @@
|
||||||
|
#!/bin/env -S deno run -A
|
||||||
|
|
||||||
|
import { encodeBase64 } from "https://deno.land/std@0.222.1/encoding/base64.ts";
|
||||||
|
import { assert } from "https://deno.land/std@0.222.1/assert/assert.ts";
|
||||||
|
import { assertEquals } from "https://deno.land/std@0.222.1/assert/assert_equals.ts";
|
||||||
|
import { crayon } from "https://deno.land/x/crayon@3.3.3/mod.ts";
|
||||||
|
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let { token } = parse_args(Deno.args);
|
||||||
|
if (token === undefined) token = prompt_args();
|
||||||
|
const client = new ApiClient("gitlab.cri.epita.fr", token);
|
||||||
|
const merge_requests = await get_all_merge_requests(client);
|
||||||
|
const total = sum(await parallel(merge_requests, (mr) => count_in_mr(mr, client)));
|
||||||
|
console.log("Total 'oki'", total);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_args(args: string[]) {
|
||||||
|
const [token_part] = args;
|
||||||
|
const token = token_part as string | undefined;
|
||||||
|
return { token };
|
||||||
|
}
|
||||||
|
|
||||||
|
function prompt_args() {
|
||||||
|
const [bl, gr] = [crayon.blue, crayon.lightBlack];
|
||||||
|
const message = `
|
||||||
|
${bl("Please enter a read-allowed gitlab token :")}
|
||||||
|
${gr("You can get one from https://gitlab.cri.epita.fr/-/user_settings/personal_access_tokens")}
|
||||||
|
${gr("with at least the permission 'read_api', format is :")}
|
||||||
|
${gr("> xxxxxxxxxxxxxxxxxxxx")}
|
||||||
|
>`.trim();
|
||||||
|
const result = prompt(message);
|
||||||
|
assert(result !== null);
|
||||||
|
return result.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiClient {
|
||||||
|
url;
|
||||||
|
token;
|
||||||
|
constructor(hostname: string, token: string) {
|
||||||
|
this.url = `https://${hostname}`;
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch(path: string) {
|
||||||
|
console.log(crayon.lightBlack("\tfetching", crayon.bold(path)));
|
||||||
|
const headers = new Headers([["PRIVATE-TOKEN", this.token]]);
|
||||||
|
const result = await fetch(this.url + "/" + path, { headers });
|
||||||
|
const parsed = await result.json();
|
||||||
|
return parsed as unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch_parse<T>(path: string, parser: z.ZodType<T>) {
|
||||||
|
const unkn = await this.fetch(path);
|
||||||
|
return parser.parse(unkn);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch_parse_paginated<T>(path: string, parser: z.ZodType<T>) {
|
||||||
|
const result = [] as T[];
|
||||||
|
let page = 1;
|
||||||
|
while (true) {
|
||||||
|
const fetched = await this.fetch_parse(path + "?per_page=100&page=" + page, parser.array());
|
||||||
|
if (fetched.length === 0) break;
|
||||||
|
result.push(...fetched);
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MergeRequest = z.infer<typeof merge_request_parser>;
|
||||||
|
const merge_request_parser = z.object({
|
||||||
|
iid: z.number(),
|
||||||
|
project_id: z.number(),
|
||||||
|
reviewers: z.array(z.object({
|
||||||
|
username: z.string(),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get_all_merge_requests(client: ApiClient): Promise<MergeRequest[]> {
|
||||||
|
return await client.fetch_parse_paginated(`api/v4/merge_requests`, merge_request_parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sum(numbers: Iterable<number>) {
|
||||||
|
let result = 0;
|
||||||
|
for (const item of numbers) result += item;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("test_sum", () => {
|
||||||
|
assertEquals(sum([1, 2, 3]), 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function parallel<I, O>(inputs: I[], operation: (item: I) => Promise<O>) {
|
||||||
|
const promises = [] as Promise<O>[];
|
||||||
|
for (const input of inputs) promises.push(operation(input));
|
||||||
|
return await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function count_in_mr(mr: MergeRequest, client: ApiClient) {
|
||||||
|
let total = 0;
|
||||||
|
const mr_path = `api/v4/projects/${mr.project_id}/merge_requests/${mr.iid}`;
|
||||||
|
const all_notes = await client.fetch_parse_paginated(`${mr_path}/notes`, note_parser);
|
||||||
|
for (const c of all_notes) {
|
||||||
|
if (!await is_relevant(c.author.username)) continue;
|
||||||
|
if (c.body.toLowerCase().includes("oki")) total += 1;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
const note_parser = z.object({
|
||||||
|
body: z.string(),
|
||||||
|
author: z.object({
|
||||||
|
username: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function hash(namespace: string) {
|
||||||
|
const buffer = new TextEncoder().encode(namespace);
|
||||||
|
const hash = await crypto.subtle.digest("SHA-512", buffer);
|
||||||
|
const base64 = encodeBase64(hash);
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function is_relevant(text: string) {
|
||||||
|
return (await hash(text)) === relevant_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relevant_hash = "Qa4xy8yRPnBsQf8U2UUE7tse2u4Fi/Aqxbt" +
|
||||||
|
"6aq56m5ofcmCkTi8PbgMBF1OtHC6qkXDF2tz2qKprYCIAMzTSQQ==";
|
||||||
|
|
||||||
|
Deno.test("encode_namespace", async () => {
|
||||||
|
const namespace = "CHANGEME";
|
||||||
|
const base64 = await hash(namespace);
|
||||||
|
console.log({ namespace, base64 });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.main) await main();
|
Loading…
Add table
Add a link
Reference in a new issue