Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Default labels #506

Merged
merged 6 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/ubiquibot-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ priority-labels:
weight: 4
- name: "Priority: 4 (Emergency)"
weight: 5
default-labels:
global:
- "Time: <1 Hour"
- "Priority: 0 (Normal)"
- "Test"
users:
pavlovcik:
Copy link
Contributor

@0xcodercrane 0xcodercrane Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh no. seems better to use @ubiquity-bounties instead of @pavlovcik lol

- "Time: <1 Hour"
- "Priority: 0 (Normal)"
Copy link
Member

@0x4007 0x4007 Jul 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool you implemented this but I still fear that we'll need to remove this when we implement the database style config so that we don't confuse future partners when they have conflicting settings e.g.

/default-labels @pavlovcik "Time: <1 Hour" "Priority: 0 (Normal)"
  users:
    pavlovcik:
      - "Time: <1 Day"
      - "Priority: 1 (Medium)"

Copy link
Member

@0x4007 0x4007 Jul 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's one thing I forgot to highlight @whilefoo but #500 (comment)

Would you mind refactoring this config to be like this:

Suggested change
default-labels:
global:
- "Time: <1 Hour"
- "Priority: 0 (Normal)"
- "Test"
users:
pavlovcik:
- "Time: <1 Hour"
- "Priority: 0 (Normal)"
default-labels:
- "Time: <1 Hour"
- "Priority: 0 (Normal)"
- "Test"

I was on the fence about letting this slide but as I think about it more I'm just concerned that it'll cause conflicting config issues when we implement the slash command.

auto-pay-mode: true
analytics-mode: true
max-concurrent-bounties: 2
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did a great job here thanks.


`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:
Expand Down
2 changes: 2 additions & 0 deletions src/bindings/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const loadConfig = async (context: Context): Promise<BotConfig> => {
incentiveMode,
chainId,
issueCreatorMultiplier,
defaultLabels,
} = await getWideConfig(context);

const publicKey = await getScalarKey(process.env.X25519_PRIVATE_KEY);
Expand All @@ -36,6 +37,7 @@ export const loadConfig = async (context: Context): Promise<BotConfig> => {
timeLabels,
priorityLabels,
commentElementPricing,
defaultLabels,
},
payout: {
chainId: chainId,
Expand Down
4 changes: 4 additions & 0 deletions src/configs/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ export const DefaultPriceConfig: PriceConfig = {
[MarkdownItem.Code]: 5,
[MarkdownItem.Image]: 5,
},
defaultLabels: {
global: [],
users: {},
},
};
20 changes: 12 additions & 8 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -66,16 +66,20 @@ export const issueCreatedCallback = async (): Promise<void> => {
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);
}
Expand Down
6 changes: 6 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ export type LabelItem = Static<typeof LabelItemSchema>;
const CommentElementPricingSchema = Type.Record(Type.String(), Type.Number());
export type CommentElementPricing = Static<typeof CommentElementPricingSchema>;

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<typeof PriceConfigSchema>;

Expand Down
15 changes: 14 additions & 1 deletion src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -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"]))) {
Expand Down Expand Up @@ -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: {},
};
}
};
11 changes: 11 additions & 0 deletions src/utils/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getPriorityLabels,
getTimeLabels,
getCommentItemPrice,
getDefaultLabels,
} from "./helpers";

const CONFIG_REPO = "ubiquibot-config";
Expand Down Expand Up @@ -46,6 +47,14 @@ export interface WideLabel {
value?: number | undefined;
}

type Label = string;
type Username = string;

export interface DefaultLabels {
global: Label[];
users: Record<Username, Label[]>;
}

export interface WideConfig {
"chain-id"?: number;
"base-multiplier"?: number;
Expand All @@ -57,6 +66,7 @@ export interface WideConfig {
"incentive-mode"?: boolean;
"max-concurrent-bounties"?: number;
"comment-element-pricing"?: Record<string, number>;
"default-labels"?: DefaultLabels;
}

export type WideRepoConfig = WideConfig;
Expand Down Expand Up @@ -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;
Expand Down