From 23de96f4207b5271c3fd774a38d09b8b0e7585e4 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 13 Jul 2023 19:33:06 +0200 Subject: [PATCH 1/4] feat: default labels config --- .github/ubiquibot-config.yml | 8 ++++++++ src/bindings/config.ts | 2 ++ src/configs/price.ts | 4 ++++ src/handlers/comment/handlers/index.ts | 20 ++++++++++++-------- src/types/config.ts | 6 ++++++ src/utils/helpers.ts | 15 ++++++++++++++- src/utils/private.ts | 11 +++++++++++ 7 files changed, 57 insertions(+), 9 deletions(-) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index 48a9f91c4..9bd09b0ba 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -28,6 +28,14 @@ priority-labels: weight: 4 - name: "Priority: 4 (Emergency)" weight: 5 +default-labels: + global: + - "Time: <1 Hour" + - "Priority: 0 (Normal)" + users: + pavlovcik: + - "Time: <1 Hour" + - "Priority: 0 (Normal)" auto-pay-mode: true analytics-mode: true max-concurrent-bounties: 2 diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 05d7b64c9..386a96fc2 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -20,6 +20,7 @@ export const loadConfig = async (context: Context): Promise => { incentiveMode, chainId, issueCreatorMultiplier, + defaultLabels, } = await getWideConfig(context); const publicKey = await getScalarKey(process.env.X25519_PRIVATE_KEY); @@ -36,6 +37,7 @@ export const loadConfig = async (context: Context): Promise => { timeLabels, priorityLabels, commentElementPricing, + defaultLabels, }, payout: { chainId: chainId, diff --git a/src/configs/price.ts b/src/configs/price.ts index d67c3fb5a..8065168ca 100644 --- a/src/configs/price.ts +++ b/src/configs/price.ts @@ -60,4 +60,8 @@ export const DefaultPriceConfig: PriceConfig = { [MarkdownItem.Code]: 5, [MarkdownItem.Image]: 5, }, + defaultLabels: { + global: [], + users: {}, + }, }; diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index c5fd31427..335ace3c3 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -9,7 +9,7 @@ import { unassign } from "./unassign"; import { registerWallet } from "./wallet"; import { setAccess } from "./set-access"; import { multiplier } from "./multiplier"; -import { addCommentToIssue, createLabel, addLabelToIssue } from "../../../helpers"; +import { addCommentToIssue, createLabel, addLabelToIssue, getLabel } from "../../../helpers"; import { getBotContext } from "../../../bindings"; import { handleIssueClosed } from "../../payout"; @@ -66,16 +66,20 @@ export const issueCreatedCallback = async (): Promise => { if (!issue) return; const labels = issue.labels; try { - const timeLabelConfigs = config.price.timeLabels.sort((label1, label2) => label1.weight - label2.weight); - const priorityLabelConfigs = config.price.priorityLabels.sort((label1, label2) => label1.weight - label2.weight); + let defaultLabels = config.price.defaultLabels.global; + const userLabels = config.price.defaultLabels.users[issue.user.login]; + if (userLabels) defaultLabels = userLabels; + const timeLabels = config.price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); const priorityLabels = config.price.priorityLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); - if (timeLabels.length === 0 && timeLabelConfigs.length > 0) await createLabel(timeLabelConfigs[0].name); - if (priorityLabels.length === 0 && priorityLabelConfigs.length > 0) await createLabel(priorityLabelConfigs[0].name); - await addLabelToIssue(timeLabelConfigs[0].name); - await addLabelToIssue(priorityLabelConfigs[0].name); - return; + if (timeLabels.length === 0 && priorityLabels.length === 0) { + for (const label of defaultLabels) { + const exists = await getLabel(label); + if (!exists) await createLabel(label); + await addLabelToIssue(label); + } + } } catch (err: unknown) { return await addCommentToIssue(`Error: ${err}`, issue.number); } diff --git a/src/types/config.ts b/src/types/config.ts index 56d48079a..62b4e2065 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -10,12 +10,18 @@ export type LabelItem = Static; const CommentElementPricingSchema = Type.Record(Type.String(), Type.Number()); export type CommentElementPricing = Static; +export const DefaultLabelsSchema = Type.Object({ + global: Type.Array(Type.String()), + users: Type.Record(Type.String(), Type.Array(Type.String())), +}); + export const PriceConfigSchema = Type.Object({ baseMultiplier: Type.Number(), issueCreatorMultiplier: Type.Number(), timeLabels: Type.Array(LabelItemSchema), priorityLabels: Type.Array(LabelItemSchema), commentElementPricing: CommentElementPricingSchema, + defaultLabels: DefaultLabelsSchema, }); export type PriceConfig = Static; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index ec8f0d57d..ef09f7110 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,6 @@ import { DEFAULT_CHAIN_ID, DefaultPriceConfig } from "../configs"; import { CommentElementPricing } from "../types"; -import { WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; +import { DefaultLabels, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; export const getChainId = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): number => { if (parsedRepo && parsedRepo["chain-id"] && !Number.isNaN(Number(parsedRepo["chain-id"]))) { @@ -101,3 +101,16 @@ export const getBountyHunterMax = (parsedRepo: WideRepoConfig | undefined, parse return 2; } }; + +export const getDefaultLabels = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): DefaultLabels => { + if (parsedRepo && parsedRepo["default-labels"]) { + return parsedRepo["default-labels"]; + } else if (parsedOrg && parsedOrg["default-labels"]) { + return parsedOrg["default-labels"]; + } else { + return { + global: [], + users: {}, + }; + } +}; diff --git a/src/utils/private.ts b/src/utils/private.ts index ab0cab303..be284cd36 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -13,6 +13,7 @@ import { getPriorityLabels, getTimeLabels, getCommentItemPrice, + getDefaultLabels, } from "./helpers"; const CONFIG_REPO = "ubiquibot-config"; @@ -46,6 +47,14 @@ export interface WideLabel { value?: number | undefined; } +type Label = string; +type Username = string; + +export interface DefaultLabels { + global: Label[]; + users: Record; +} + export interface WideConfig { "chain-id"?: number; "base-multiplier"?: number; @@ -57,6 +66,7 @@ export interface WideConfig { "incentive-mode"?: boolean; "max-concurrent-bounties"?: number; "comment-element-pricing"?: Record; + "default-labels"?: DefaultLabels; } export type WideRepoConfig = WideConfig; @@ -137,6 +147,7 @@ export const getWideConfig = async (context: Context) => { bountyHunterMax: getBountyHunterMax(parsedRepo, parsedOrg), incentiveMode: getIncentiveMode(parsedRepo, parsedOrg), commentElementPricing: getCommentItemPrice(parsedRepo, parsedOrg), + defaultLabels: getDefaultLabels(parsedRepo, parsedOrg), }; return configData; From 89c8b525bc4b6e6104453e8a660f7ed151a44883 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 13 Jul 2023 19:50:08 +0200 Subject: [PATCH 2/4] feat: test label --- .github/ubiquibot-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index 9bd09b0ba..efc40b555 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -32,6 +32,7 @@ default-labels: global: - "Time: <1 Hour" - "Priority: 0 (Normal)" + - "Test" users: pavlovcik: - "Time: <1 Hour" From 8070152a356d66277e14e90be0852d7a48f38a8e Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 13 Jul 2023 23:02:09 +0200 Subject: [PATCH 3/4] feat: add documentation for config --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 5ad4a1eeb..5942c31df 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,40 @@ To test the bot, you can: 2. Add a time label, ex: `Time: <1 Day` 3. At this point the bot should add a price label. +## Configuration + +`chain-id` is ID of the EVM-compatible network that will be used for payouts. + +`base-multiplier` is a base number that will be used to calculate bounty price based on the following formula: `price = base-multiplier * time-label-weight * priority-label-weight / 10` + +`time-labels` are labels for marking the time limit of the bounty: + +- `name` is a human-readable name +- `weight` is a number that will be used to calculate the bounty price +- `value` is number of seconds that corresponds to the time limit of the bounty + +`priority-labels` are labels for marking the priority of the bounty: + +- `name` is a human-readable name +- `weight` is a number that will be used to calculate the bounty price + +`default-labels` are labels that are applied when an issue is created without any time or priority labels. + +- `global` are global default labels +- `users` are user-specific labels that override the global default labels + +`auto-pay-mode` can be `true` or `false` that enables or disables automatic payout of bounties when the issue is closed. + +`analytics-mode` can be `true` or `false` that enables or disables weekly analytics collection by Ubiquity. + +`incentive-mode` can be `true` or `false` that enables or disables comment incentives. These are comments in the issue by either the creator of the bounty or other users. + +`issue-creator-multiplier` is a number that defines a base multiplier for calculating incentive reward for the creator of the issue. + +`comment-element-pricing` defines how much is a part of the comment worth. For example `text: 0.1` means that any text in the comment will be multiplied by 0.1 + +`max-concurrent-bounties` is the maximum number of bounties that can be assigned to a bounty hunter at once. This excludes bounties with pending pull request reviews. + ## How to run locally 1. Create a new project at [Supabase](https://supabase.com/). Add `Project URL` and `API Key` to the `.env` file: From 05a0c4704201833af935e4e0226cacf78cc00507 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 16 Jul 2023 11:36:15 +0200 Subject: [PATCH 4/4] feat: remove user specific labels --- .github/ubiquibot-config.yml | 11 +++-------- README.md | 3 --- src/configs/price.ts | 5 +---- src/handlers/comment/handlers/index.ts | 6 +----- src/types/config.ts | 7 +------ src/utils/helpers.ts | 9 +++------ src/utils/private.ts | 10 +--------- 7 files changed, 10 insertions(+), 41 deletions(-) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index efc40b555..57fb40d84 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -29,14 +29,9 @@ priority-labels: - name: "Priority: 4 (Emergency)" weight: 5 default-labels: - global: - - "Time: <1 Hour" - - "Priority: 0 (Normal)" - - "Test" - users: - pavlovcik: - - "Time: <1 Hour" - - "Priority: 0 (Normal)" + - "Time: <1 Hour" + - "Priority: 0 (Normal)" + - "Test" auto-pay-mode: true analytics-mode: true max-concurrent-bounties: 2 diff --git a/README.md b/README.md index 5942c31df..0311933d0 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,6 @@ To test the bot, you can: `default-labels` are labels that are applied when an issue is created without any time or priority labels. -- `global` are global default labels -- `users` are user-specific labels that override the global default labels - `auto-pay-mode` can be `true` or `false` that enables or disables automatic payout of bounties when the issue is closed. `analytics-mode` can be `true` or `false` that enables or disables weekly analytics collection by Ubiquity. diff --git a/src/configs/price.ts b/src/configs/price.ts index 8065168ca..378001c56 100644 --- a/src/configs/price.ts +++ b/src/configs/price.ts @@ -60,8 +60,5 @@ export const DefaultPriceConfig: PriceConfig = { [MarkdownItem.Code]: 5, [MarkdownItem.Image]: 5, }, - defaultLabels: { - global: [], - users: {}, - }, + defaultLabels: [], }; diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 335ace3c3..ca352f2e2 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -66,15 +66,11 @@ export const issueCreatedCallback = async (): Promise => { if (!issue) return; const labels = issue.labels; try { - let defaultLabels = config.price.defaultLabels.global; - const userLabels = config.price.defaultLabels.users[issue.user.login]; - if (userLabels) defaultLabels = userLabels; - const timeLabels = config.price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); const priorityLabels = config.price.priorityLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); if (timeLabels.length === 0 && priorityLabels.length === 0) { - for (const label of defaultLabels) { + for (const label of config.price.defaultLabels) { const exists = await getLabel(label); if (!exists) await createLabel(label); await addLabelToIssue(label); diff --git a/src/types/config.ts b/src/types/config.ts index 62b4e2065..a06ca27c6 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -10,18 +10,13 @@ export type LabelItem = Static; const CommentElementPricingSchema = Type.Record(Type.String(), Type.Number()); export type CommentElementPricing = Static; -export const DefaultLabelsSchema = Type.Object({ - global: Type.Array(Type.String()), - users: Type.Record(Type.String(), Type.Array(Type.String())), -}); - export const PriceConfigSchema = Type.Object({ baseMultiplier: Type.Number(), issueCreatorMultiplier: Type.Number(), timeLabels: Type.Array(LabelItemSchema), priorityLabels: Type.Array(LabelItemSchema), commentElementPricing: CommentElementPricingSchema, - defaultLabels: DefaultLabelsSchema, + defaultLabels: Type.Array(Type.String()), }); export type PriceConfig = Static; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index ef09f7110..79a3f6008 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,6 @@ import { DEFAULT_CHAIN_ID, DefaultPriceConfig } from "../configs"; import { CommentElementPricing } from "../types"; -import { DefaultLabels, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; +import { WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; export const getChainId = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): number => { if (parsedRepo && parsedRepo["chain-id"] && !Number.isNaN(Number(parsedRepo["chain-id"]))) { @@ -102,15 +102,12 @@ export const getBountyHunterMax = (parsedRepo: WideRepoConfig | undefined, parse } }; -export const getDefaultLabels = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): DefaultLabels => { +export const getDefaultLabels = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): string[] => { if (parsedRepo && parsedRepo["default-labels"]) { return parsedRepo["default-labels"]; } else if (parsedOrg && parsedOrg["default-labels"]) { return parsedOrg["default-labels"]; } else { - return { - global: [], - users: {}, - }; + return []; } }; diff --git a/src/utils/private.ts b/src/utils/private.ts index be284cd36..05211c4c3 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -47,14 +47,6 @@ export interface WideLabel { value?: number | undefined; } -type Label = string; -type Username = string; - -export interface DefaultLabels { - global: Label[]; - users: Record; -} - export interface WideConfig { "chain-id"?: number; "base-multiplier"?: number; @@ -66,7 +58,7 @@ export interface WideConfig { "incentive-mode"?: boolean; "max-concurrent-bounties"?: number; "comment-element-pricing"?: Record; - "default-labels"?: DefaultLabels; + "default-labels"?: string[]; } export type WideRepoConfig = WideConfig;