diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index 655095e6f..c0c0f81f1 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -28,6 +28,10 @@ priority-labels: weight: 4 - name: "Priority: 4 (Emergency)" weight: 5 +default-labels: + - "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 5ad4a1eeb..0311933d0 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,37 @@ 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. + +`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: diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 60e0eb5dd..fb471c3dd 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -20,6 +20,7 @@ export const loadConfig = async (context: Context): Promise => { incentiveMode, networkId, issueCreatorMultiplier, + defaultLabels, promotionComment, } = await getWideConfig(context); @@ -37,6 +38,7 @@ export const loadConfig = async (context: Context): Promise => { timeLabels, priorityLabels, commentElementPricing, + defaultLabels, }, comments: { promotionComment: promotionComment, diff --git a/src/configs/price.ts b/src/configs/price.ts index d67c3fb5a..378001c56 100644 --- a/src/configs/price.ts +++ b/src/configs/price.ts @@ -60,4 +60,5 @@ export const DefaultPriceConfig: PriceConfig = { [MarkdownItem.Code]: 5, [MarkdownItem.Image]: 5, }, + defaultLabels: [], }; diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 6d3e0928c..d1de30521 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"; @@ -70,16 +70,16 @@ 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); 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 config.price.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 d24cfd52b..12ac87825 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -16,6 +16,7 @@ export const PriceConfigSchema = Type.Object({ timeLabels: Type.Array(LabelItemSchema), priorityLabels: Type.Array(LabelItemSchema), commentElementPricing: CommentElementPricingSchema, + defaultLabels: Type.Array(Type.String()), }); export type PriceConfig = Static; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index f0518ad1a..0401e5276 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -111,3 +111,13 @@ export const getBountyHunterMax = (parsedRepo: WideRepoConfig | undefined, parse return 2; } }; + +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 []; + } +}; diff --git a/src/utils/private.ts b/src/utils/private.ts index ff3952cd4..e0b6fe795 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -13,6 +13,7 @@ import { getPriorityLabels, getTimeLabels, getCommentItemPrice, + getDefaultLabels, getPromotionComment, } from "./helpers"; @@ -59,6 +60,7 @@ export interface WideConfig { "incentive-mode"?: boolean; "max-concurrent-bounties"?: number; "comment-element-pricing"?: Record; + "default-labels"?: string[]; } export type WideRepoConfig = WideConfig; @@ -139,6 +141,7 @@ export const getWideConfig = async (context: Context) => { bountyHunterMax: getBountyHunterMax(parsedRepo, parsedOrg), incentiveMode: getIncentiveMode(parsedRepo, parsedOrg), commentElementPricing: getCommentItemPrice(parsedRepo, parsedOrg), + defaultLabels: getDefaultLabels(parsedRepo, parsedOrg), promotionComment: getPromotionComment(parsedRepo, parsedOrg), };