Skip to content

Commit

Permalink
Merge pull request #24 from ubiquity-os-marketplace/development
Browse files Browse the repository at this point in the history
Merge development into main
  • Loading branch information
gentlementlegen authored Oct 21, 2024
2 parents 5653a0a + e7040d2 commit c22e37d
Show file tree
Hide file tree
Showing 19 changed files with 412 additions and 167 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/compute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ on:
description: "Auth Token"
ref:
description: "Ref"
signature:
description: "The kernel signature"

jobs:
compute:
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/update-configuration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: "Update Configuration"

on:
workflow_dispatch:
push:

jobs:
update:
name: "Update Configuration in manifest.json"
runs-on: ubuntu-latest
permissions: write-all

steps:
- uses: actions/checkout@v4

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: "20.10.0"

- name: Install deps and run configuration update
run: |
yarn install --immutable --immutable-cache --check-cache
yarn tsc --noCheck --project tsconfig.json
- name: Update manifest configuration using GitHub Script
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const { pluginSettingsSchema } = require('./src/types');
const manifestPath = path.resolve("${{ github.workspace }}", './manifest.json');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
const configuration = JSON.stringify(pluginSettingsSchema);
manifest["configuration"] = JSON.parse(configuration);
const updatedManifest = JSON.stringify(manifest, null, 2)
console.log('Updated manifest:', updatedManifest);
fs.writeFileSync(manifestPath, updatedManifest);
- name: Commit and Push generated types
run: |
git config --global user.name 'ubiquity-os[bot]'
git config --global user.email 'ubiquity-os[bot]@users.noreply.github.com'
git add ./manifest.json
if [ -n "$(git diff-index --cached --name-only HEAD)" ]; then
git commit -m "chore: updated generated configuration" || echo "Lint-staged check failed"
git push origin HEAD:${{ github.ref_name }}
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
10 changes: 4 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@

## 1.0.0 (2024-07-29)


### Features

* database update step ([5bad80a](https://github.com/ubiquibot/automated-merging/commit/5bad80a8049890dcf16a5661caadfdacc89fdf2b))
* set db to be sqlite ([2dbe73b](https://github.com/ubiquibot/automated-merging/commit/2dbe73be10f9ae436050f6b3626890db847c166c))

- database update step ([5bad80a](https://github.com/ubiquibot/automated-merging/commit/5bad80a8049890dcf16a5661caadfdacc89fdf2b))
- set db to be sqlite ([2dbe73b](https://github.com/ubiquibot/automated-merging/commit/2dbe73be10f9ae436050f6b3626890db847c166c))

### Bug Fixes

* changed approval requirement check to use the configuration ([e1f50e9](https://github.com/ubiquibot/automated-merging/commit/e1f50e95576f81ce01196bbdc0890b0617bf23df))
* fixed imports within main ([bb001cf](https://github.com/ubiquibot/automated-merging/commit/bb001cf3204593a79b2d214941940a9a44675c00))
- changed approval requirement check to use the configuration ([e1f50e9](https://github.com/ubiquibot/automated-merging/commit/e1f50e95576f81ce01196bbdc0890b0617bf23df))
- fixed imports within main ([bb001cf](https://github.com/ubiquibot/automated-merging/commit/bb001cf3204593a79b2d214941940a9a44675c00))
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `@ubiquibot/automated-merging`

Automatically merge pull-requests based on the reviewer count, the time elapsed since the last activity, depending
Automatically merge pull-requests based on the reviewer count, the time elapsed since the last activity, depending
on the association of the pull-request author.

## Configuration example
Expand All @@ -17,6 +17,10 @@ on the association of the pull-request author.
mergeTimeout:
collaborator: "3.5 days" # defaults to 3.5 days
contributor: "7 days" # defaults to 7 days
repos:
monitor: ["ubiquibot/automated-merging"]
ignore: ["ubiquibot/automated-merging"]
allowedReviewerRoles: ["COLLABORATOR", "MEMBER", "OWNER"]
```
## Testing
Expand Down
86 changes: 84 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,87 @@
{
"name": "Automated merging",
"description": "Automatically merge pull-requests.",
"ubiquity:listeners": [ "push", "issue_comment.created" ]
}
"ubiquity:listeners": [
"push",
"issue_comment.created"
],
"configuration": {
"type": "object",
"properties": {
"approvalsRequired": {
"default": {},
"type": "object",
"properties": {
"collaborator": {
"default": 1,
"minimum": 1,
"type": "number"
},
"contributor": {
"default": 2,
"minimum": 1,
"type": "number"
}
},
"required": [
"collaborator",
"contributor"
]
},
"mergeTimeout": {
"default": {},
"type": "object",
"properties": {
"collaborator": {
"default": "3.5 days",
"type": "string"
},
"contributor": {
"default": "7 days",
"type": "string"
}
},
"required": [
"collaborator",
"contributor"
]
},
"repos": {
"default": {},
"type": "object",
"properties": {
"monitor": {
"default": [],
"type": "array",
"items": {
"minLength": 1,
"type": "string"
}
},
"ignore": {
"default": [],
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"monitor",
"ignore"
]
},
"allowedReviewerRoles": {
"default": [
"COLLABORATOR",
"MEMBER",
"OWNER"
],
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@
"npm-run-all": "4.1.5",
"prettier": "3.3.2",
"ts-jest": "29.1.5",
"typescript": "5.4.5",
"typescript-eslint": "7.13.1"
"typescript": "5.6.2",
"typescript-eslint": "8.8.0"
},
"lint-staged": {
"*.ts": [
Expand All @@ -84,5 +84,6 @@
"extends": [
"@commitlint/config-conventional"
]
}
}
},
"packageManager": "[email protected]"
}
34 changes: 7 additions & 27 deletions src/action.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as github from "@actions/github";
import { Octokit } from "@octokit/rest";
import { Value } from "@sinclair/typebox/value";
import { validateAndDecodeSchemas } from "./helpers/validator";
import { plugin } from "./plugin";
import { envSchema, envValidator, PluginInputs, pluginSettingsSchema, pluginSettingsValidator } from "./types";
import { PluginInputs } from "./types";

/**
* How a GitHub action executes the plugin.
Expand All @@ -11,47 +11,27 @@ export async function run() {
const payload = github.context.payload.inputs;

payload.env = { ...(payload.env || {}), workflowName: github.context.workflow };
if (!envValidator.test(payload.env)) {
const errors: string[] = [];
for (const error of envValidator.errors(payload.env)) {
console.error(error);
errors.push(`${error.path}: ${error.message}`);
}
throw new Error(`Invalid environment provided:\n${errors.join(";\n")}`);
}
const env = Value.Decode(envSchema, payload.env || {});

payload.settings = Value.Default(pluginSettingsSchema, JSON.parse(payload.settings));
if (!pluginSettingsValidator.test(payload.settings)) {
const errors: string[] = [];
for (const error of pluginSettingsValidator.errors(payload.settings)) {
console.error(error);
errors.push(`${error.path}: ${error.message}`);
}
throw new Error(`Invalid settings provided:\n${errors.join(";\n")}`);
}

const settings = Value.Decode(pluginSettingsSchema, payload.settings);
const { decodedSettings, decodedEnv } = validateAndDecodeSchemas(payload.env, JSON.parse(payload.settings));
const inputs: PluginInputs = {
stateId: payload.stateId,
eventName: payload.eventName,
eventPayload: JSON.parse(payload.eventPayload),
settings,
settings: decodedSettings,
authToken: payload.authToken,
ref: payload.ref,
};

await plugin(inputs, env);
await plugin(inputs, decodedEnv);

return returnDataToKernel(process.env.GITHUB_TOKEN, inputs.stateId, {});
}

async function returnDataToKernel(repoToken: string, stateId: string, output: object) {
export async function returnDataToKernel(repoToken: string, stateId: string, output: object, eventType = "return-data-to-ubiquity-os-kernel") {
const octokit = new Octokit({ auth: repoToken });
return octokit.repos.createDispatchEvent({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
event_type: "return_data_to_ubiquibot_kernel",
event_type: eventType,
client_payload: {
state_id: stateId,
output: JSON.stringify(output),
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface ResultInfo {

function generateGitHubSummary(context: Context, urls: ResultInfo[]): string {
const target = `https://github.com/${context.payload.repository.owner?.login}`;
const output: string[] = ["## Merge report\n\n"];
const output: (string | undefined)[] = ["## Merge report\n\n"];
output.push("<samp>\n");
output.push("| Merged | ID |");
output.push("|---|---|");
Expand All @@ -29,9 +29,9 @@ function generateGitHubSummary(context: Context, urls: ResultInfo[]): string {
output.push("\n</samp>\n");
output.push("## Configuration\n\n");
output.push("### Watching Repositories\n\n");
output.push(context.config.repos.monitor.map((o) => `- [${o}](${target}/${o})`).join("\n"));
output.push(context.config.repos?.monitor.map((o) => `- [${o}](${target}/${o})`).join("\n"));
output.push("### Ignored Repositories\n\n");
output.push(context.config.repos.ignore.map((o) => `- [${o}](${target}/${o})`).join("\n"));
output.push(context.config.repos?.ignore.map((o) => `- [${o}](${target}/${o})`).join("\n"));
return output.join("\n");
}

Expand Down
23 changes: 16 additions & 7 deletions src/helpers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,35 @@ export interface Requirements {
* Gets the merge timeout depending on the status of the assignee. If there are multiple assignees with different
* statuses, the longest timeout is chosen.
*/
export async function getMergeTimeoutAndApprovalRequiredCount(context: Context, authorAssociation: string): Promise<Requirements> {
export async function getMergeTimeoutAndApprovalRequiredCount(context: Context, authorAssociation: string) {
const {
config: { mergeTimeout, approvalsRequired },
} = context;
const timeoutCollaborator = {
mergeTimeout: context.config.mergeTimeout.collaborator,
requiredApprovalCount: context.config.approvalsRequired.collaborator,
mergeTimeout: mergeTimeout?.collaborator,
requiredApprovalCount: approvalsRequired?.collaborator,
};
const timeoutContributor = {
mergeTimeout: context.config.mergeTimeout.contributor,
requiredApprovalCount: context.config.approvalsRequired.contributor,
mergeTimeout: mergeTimeout?.contributor,
requiredApprovalCount: approvalsRequired?.contributor,
};

/**
* Hardcoded roles here because we need to determine the timeouts
* separate from `allowedReviewerRoles` which introduces
* potential unintended user errors and logic issues.
*/
return ["COLLABORATOR", "MEMBER", "OWNER"].includes(authorAssociation) ? timeoutCollaborator : timeoutContributor;
}

export async function getApprovalCount({ octokit, logger }: Context, { owner, repo, issue_number: pullNumber }: IssueParams) {
export async function getApprovalCount({ octokit, logger, config: { allowedReviewerRoles } }: Context, { owner, repo, issue_number: pullNumber }: IssueParams) {
try {
const { data: reviews } = await octokit.rest.pulls.listReviews({
owner,
repo,
pull_number: pullNumber,
});
return reviews.filter((review) => review.state === "APPROVED").length;
return reviews.filter((review) => allowedReviewerRoles?.includes(review.author_association)).filter((review) => review.state === "APPROVED").length;
} catch (e) {
logger.error(`Error fetching reviews' approvals: ${e}`);
return 0;
Expand Down
16 changes: 11 additions & 5 deletions src/helpers/update-pull-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RestEndpointMethodTypes } from "@octokit/rest";
import ms from "ms";
import { getAllTimelineEvents } from "../handlers/github-events";
import { generateSummary, ResultInfo } from "../handlers/summary";
import { Context } from "../types";
import { Context, ReposWatchSettings } from "../types";
import {
getApprovalCount,
getMergeTimeoutAndApprovalRequiredCount,
Expand Down Expand Up @@ -30,7 +30,7 @@ export async function updatePullRequests(context: Context) {
const { logger } = context;
const results: ResultInfo[] = [];

if (!context.config.repos.monitor.length) {
if (!context.config.repos?.monitor.length) {
const owner = context.payload.repository.owner;
if (owner) {
logger.info(`No organizations or repo have been specified, will default to the organization owner: ${owner.login}.`);
Expand All @@ -39,7 +39,7 @@ export async function updatePullRequests(context: Context) {
}
}

const pullRequests = await getOpenPullRequests(context, context.config.repos);
const pullRequests = await getOpenPullRequests(context, context.config.repos as ReposWatchSettings);

if (!pullRequests?.length) {
return logger.info("Nothing to do.");
Expand Down Expand Up @@ -74,8 +74,14 @@ export async function updatePullRequests(context: Context) {
);
if (isNaN(lastActivityDate.getTime())) {
logger.info(`PR ${html_url} does not seem to have any activity, nothing to do.`);
} else if (isPastOffset(lastActivityDate, requirements.mergeTimeout)) {
isMerged = await attemptMerging(context, { gitHubUrl, htmlUrl: html_url, requirements, lastActivityDate, pullRequestDetails });
} else if (requirements?.mergeTimeout && isPastOffset(lastActivityDate, requirements?.mergeTimeout)) {
isMerged = await attemptMerging(context, {
gitHubUrl,
htmlUrl: html_url,
requirements: requirements as Requirements,
lastActivityDate,
pullRequestDetails,
});
} else {
logger.info(`PR ${html_url} has activity up until (${lastActivityDate}), nothing to do.`);
}
Expand Down
Loading

0 comments on commit c22e37d

Please sign in to comment.