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

fix: derive weight automatically #627

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 6 additions & 21 deletions .github/ubiquibot-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,19 @@ price-multiplier: 1.5
issue-creator-multiplier: 2
time-labels:
- name: "Time: <1 Hour"
weight: 0.125
value: 3600
- name: "Time: <2 Hours"
weight: 0.25
value: 7200
- name: "Time: <4 Hours"
weight: 0.5
value: 14400
- name: "Time: <1 Day"
weight: 1
value: 86400
- name: "Time: <1 Week"
weight: 2
value: 604800
priority-labels:
- name: "Priority: 0 (Normal)"
weight: 1
- name: "Priority: 1 (Medium)"
weight: 2
- name: "Priority: 2 (High)"
weight: 3
- name: "Priority: 3 (Urgent)"
weight: 4
- name: "Priority: 4 (Emergency)"
weight: 5
- name: "Priority: 1 (Normal)"
- name: "Priority: 2 (Medium)"
- name: "Priority: 3 (High)"
- name: "Priority: 4 (Urgent)"
- name: "Priority: 5 (Emergency)"
default-labels:
- "Time: <1 Hour"
- "Priority: 0 (Normal)"
- "Priority: 1 (Normal)"
payment-permit-max-price: 1000
comment-incentives: true
max-concurrent-bounties: 2
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,11 @@ To test the bot, you can:
`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

`command-settings` are setting to enable or disable a command

Expand Down
6 changes: 3 additions & 3 deletions src/handlers/assign/action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import { addCommentToIssue, closePullRequest, getOpenedPullRequestsForAnIssue } from "../../helpers";
import { addCommentToIssue, closePullRequest, getOpenedPullRequestsForAnIssue, calculateWeight, calculateDuration } from "../../helpers";
import { Payload, LabelItem } from "../../types";
import { deadLinePrefix } from "../shared";

Expand Down Expand Up @@ -49,9 +49,9 @@ export const commentWithAssignMessage = async (): Promise<void> => {
return;
}

const sorted = timeLabelsAssigned.sort((a, b) => a.weight - b.weight);
const sorted = timeLabelsAssigned.sort((a, b) => calculateWeight(a) - calculateWeight(b));
const targetTimeLabel = sorted[0];
const duration = targetTimeLabel.value;
const duration = calculateDuration(targetTimeLabel);
if (!duration) {
logger.debug(`Missing configure for timelabel: ${targetTimeLabel.name}`);
return;
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/comment/handlers/assign.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addAssignees, getAssignedIssues, getAvailableOpenedPullRequests, getAllIssueComments } from "../../../helpers";
import { addAssignees, getAssignedIssues, getAvailableOpenedPullRequests, getAllIssueComments, calculateWeight, calculateDuration } from "../../../helpers";
import { getBotConfig, getBotContext, getLogger } from "../../../bindings";
import { Payload, LabelItem, Comment, IssueType, Issue } from "../../../types";
import { deadLinePrefix } from "../../shared";
Expand Down Expand Up @@ -81,9 +81,9 @@ export const assign = async (body: string) => {
return "Skipping `/start` since no time labels are set to calculate the timeline";
}

const sorted = timeLabelsAssigned.sort((a, b) => a.weight - b.weight);
const sorted = timeLabelsAssigned.sort((a, b) => calculateWeight(a) - calculateWeight(b));
const targetTimeLabel = sorted[0];
const duration = targetTimeLabel.value;
const duration = calculateDuration(targetTimeLabel);
if (!duration) {
logger.info(`Missing configure for time label: ${targetTimeLabel.name}`);
return "Skipping `/start` since configuration is missing for the following labels";
Expand Down
7 changes: 5 additions & 2 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getPayoutConfigByNetworkId,
getTokenSymbol,
getAllIssueAssignEvents,
calculateWeight,
} from "../../../helpers";
import { getBotConfig, getBotContext, getLogger } from "../../../bindings";
import { handleIssueClosed } from "../../payout";
Expand Down Expand Up @@ -97,8 +98,10 @@ export const issueCreatedCallback = async (): Promise<void> => {
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));

const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : config.price.defaultLabels[0];
const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : config.price.defaultLabels[1];
const minTimeLabel =
timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : config.price.defaultLabels[0];
const minPriorityLabel =
priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : config.price.defaultLabels[1];
if (!timeLabels.length) await addLabelToIssue(minTimeLabel);
if (!priorityLabels.length) await addLabelToIssue(minPriorityLabel);

Expand Down
6 changes: 3 additions & 3 deletions src/handlers/pricing/action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import { GLOBAL_STRINGS } from "../../configs";
import { addCommentToIssue, addLabelToIssue, clearAllPriceLabelsOnIssue, createLabel, getLabel } from "../../helpers";
import { addCommentToIssue, addLabelToIssue, clearAllPriceLabelsOnIssue, createLabel, getLabel, calculateWeight } from "../../helpers";
import { Payload } from "../../types";
import { handleLabelsAccess } from "../access";
import { getTargetPriceLabel } from "../shared";
Expand Down Expand Up @@ -33,8 +33,8 @@ export const pricingLabelLogic = async (): Promise<void> => {
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));

const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;
const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;

const targetPriceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel);
if (targetPriceLabel) {
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/pricing/pre.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getBotConfig, getLogger } from "../../bindings";
import { createLabel, listLabelsForRepo } from "../../helpers";
import { calculateWeight, createLabel, listLabelsForRepo } from "../../helpers";
import { calculateBountyPrice } from "../shared";

/**
Expand All @@ -22,7 +22,7 @@ export const validatePriceLabels = async (): Promise<void> => {
const aiLabels: string[] = [];
for (const timeLabel of config.price.timeLabels) {
for (const priorityLabel of config.price.priorityLabels) {
const targetPrice = calculateBountyPrice(timeLabel.weight, priorityLabel.weight, config.price.baseMultiplier);
const targetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier);
const targetPriceLabel = `Price: ${targetPrice} USD`;
aiLabels.push(targetPriceLabel);
}
Expand Down
5 changes: 3 additions & 2 deletions src/handlers/shared/pricing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getBotConfig } from "../../bindings";
import { calculateWeight } from "../../helpers";

export const calculateBountyPrice = (timeValue: number, priorityValue: number, baseValue?: number): number => {
const botConfig = getBotConfig();
Expand All @@ -12,8 +13,8 @@ export const getTargetPriceLabel = (timeLabel: string | undefined, priorityLabel
const botConfig = getBotConfig();
let targetPriceLabel: string | undefined = undefined;
if (timeLabel && priorityLabel) {
const timeWeight = botConfig.price.timeLabels.find((item) => item.name === timeLabel)?.weight;
const priorityWeight = botConfig.price.priorityLabels.find((item) => item.name === priorityLabel)?.weight;
const timeWeight = calculateWeight(botConfig.price.timeLabels.find((item) => item.name === timeLabel));
const priorityWeight = calculateWeight(botConfig.price.priorityLabels.find((item) => item.name === priorityLabel));
if (timeWeight && priorityWeight) {
const bountyPrice = calculateBountyPrice(timeWeight, priorityWeight);
targetPriceLabel = `Price: ${bountyPrice} USD`;
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/wildcard/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getMaxIssueNumber, upsertIssue, upsertUser } from "../../adapters/supabase";
import { getBotConfig, getLogger } from "../../bindings";
import { listIssuesForRepo, getUser } from "../../helpers";
import { listIssuesForRepo, getUser, calculateWeight } from "../../helpers";
import { Issue, IssueType, User, UserProfile } from "../../types";
import { getTargetPriceLabel } from "../shared";

Expand All @@ -24,8 +24,8 @@ export const bountyInfo = (

const isBounty = timeLabels.length > 0 && priorityLabels.length > 0;

const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;
const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;

const priceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel);

Expand Down
5 changes: 3 additions & 2 deletions src/helpers/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { COLORS } from "../configs";
import { calculateBountyPrice } from "../handlers";
import { Label, Payload } from "../types";
import { deleteLabel } from "./issue";
import { calculateWeight } from "../helpers";

export const listLabelsForRepo = async (per_page?: number, page?: number): Promise<Label[]> => {
const context = getBotContext();
Expand Down Expand Up @@ -67,11 +68,11 @@ export const updateLabelsFromBaseRate = async (owner: string, repo: string, cont

for (const timeLabel of config.price.timeLabels) {
for (const priorityLabel of config.price.priorityLabels) {
const targetPrice = calculateBountyPrice(timeLabel.weight, priorityLabel.weight, config.price.baseMultiplier);
const targetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier);
const targetPriceLabel = `Price: ${targetPrice} USD`;
newLabels.push(targetPriceLabel);

const previousTargetPrice = calculateBountyPrice(timeLabel.weight, priorityLabel.weight, previousBaseRate);
const previousTargetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), previousBaseRate);
const previousTargetPriceLabel = `Price: ${previousTargetPrice} USD`;
previousLabels.push(previousTargetPriceLabel);
}
Expand Down
26 changes: 25 additions & 1 deletion src/helpers/shared.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getBotContext } from "../bindings";
import { Payload, UserType } from "../types";
import { LabelItem, Payload, UserType } from "../types";

const contextNamesToSkip = ["workflow_run"];

Expand All @@ -19,3 +19,27 @@ export const shouldSkip = (): { skip: boolean; reason: string } => {
};

export const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));

export const calculateWeight = (label: LabelItem | undefined): number => {
if (!label) return 0;
const matches = label.name.match(/\d+/);
const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0;
if (label.name.toLowerCase().includes("priority")) return number;
if (label.name.toLowerCase().includes("hour")) return number * 0.125;
if (label.name.toLowerCase().includes("day")) return 1 + (number - 1) * 0.25;
if (label.name.toLowerCase().includes("week")) return number + 1;
if (label.name.toLowerCase().includes("month")) return 5 + (number - 1) * 8;
kamaalsultan marked this conversation as resolved.
Show resolved Hide resolved
return 0;
};

export const calculateDuration = (label: LabelItem): number => {
kamaalsultan marked this conversation as resolved.
Show resolved Hide resolved
if (!label) return 0;
const matches = label.name.match(/\d+/);
if (label.name.toLowerCase().includes("priority")) return 0;
const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0;
if (label.name.toLowerCase().includes("hour")) return number * 3600;
if (label.name.toLowerCase().includes("day")) return number * 86400;
if (label.name.toLowerCase().includes("week")) return number * 604800;
if (label.name.toLowerCase().includes("month")) return number * 2592000;
return 0;
};
2 changes: 0 additions & 2 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { Level } from "../adapters/supabase";

const LabelItemSchema = Type.Object({
name: Type.String(),
weight: Type.Number(),
value: Type.Optional(Type.Number()),
});
export type LabelItem = Static<typeof LabelItemSchema>;

Expand Down
2 changes: 0 additions & 2 deletions src/utils/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo",

export interface WideLabel {
name: string;
weight: number;
value?: number | undefined;
}

export interface CommentIncentives {
Expand Down
35 changes: 10 additions & 25 deletions ubiquibot-config-default.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,36 @@
"default-labels": [],
"time-labels": [
{
"name": "Time: <1 Hour",
"weight": 0.125,
"value": 3600
"name": "Time: <1 Hour"
},
{
"name": "Time: <1 Day",
"weight": 1,
"value": 86400
"name": "Time: <1 Day"
},
{
"name": "Time: <1 Week",
"weight": 2,
"value": 604800
"name": "Time: <1 Week"
},
{
"name": "Time: <2 Weeks",
"weight": 3,
"value": 1209600
"name": "Time: <2 Weeks"
},
{
"name": "Time: <1 Month",
"weight": 4,
"value": 2592000
"name": "Time: <1 Month"
}
],
"priority-labels": [
{
"name": "Priority: 0 (Normal)",
"weight": 1
"name": "Priority: 1 (Normal)"
},
{
"name": "Priority: 1 (Medium)",
"weight": 2
"name": "Priority: 2 (Medium)"
},
{
"name": "Priority: 2 (High)",
"weight": 3
"name": "Priority: 3 (High)"
},
{
"name": "Priority: 3 (Urgent)",
"weight": 4
"name": "Priority: 4 (Urgent)"
},
{
"name": "Priority: 4 (Emergency)",
"weight": 5
"name": "Priority: 5 (Emergency)"
}
],
"command-settings": [
Expand Down