Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge development into main #24

Merged
merged 54 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
286f1b1
fix: filter reviews by approved author_association
Keyrxng Sep 6, 2024
3b80df2
chore: add test
Keyrxng Sep 6, 2024
6445b00
chore: fix package manager field
Keyrxng Sep 6, 2024
8922bd5
chore: configurable roles with authority
Keyrxng Sep 7, 2024
13b71cb
chore: use allowedReviewerRoles
Keyrxng Sep 7, 2024
1ac6ab4
chore: hardcode timeout roles
Keyrxng Sep 12, 2024
8afbfad
Merge pull request #19 from ubq-testing/development
ubiquity-os[bot] Sep 12, 2024
447f421
feat: added schema validation workflow
gentlementlegen Sep 20, 2024
fdffadb
Merge pull request #20 from gentlementlegen/feat/schema-validation
gentlementlegen Sep 20, 2024
a5a70e2
chore: update validator.ts
gentlementlegen Sep 20, 2024
ed21a56
chore: update validator.ts
gentlementlegen Sep 20, 2024
1474a79
chore: update validator.ts
gentlementlegen Sep 20, 2024
27cdcae
chore: added logs
gentlementlegen Sep 20, 2024
a76f5dd
chore: added logs
gentlementlegen Sep 20, 2024
7e64c89
chore: added logs
gentlementlegen Sep 20, 2024
f293238
chore: added write all permission
gentlementlegen Sep 20, 2024
d0956fe
chore: added errors
gentlementlegen Sep 20, 2024
f90eca5
chore: added errors
gentlementlegen Sep 20, 2024
7303017
chore: added payload
gentlementlegen Sep 20, 2024
77d2f89
chore: added payload
gentlementlegen Sep 20, 2024
92a5667
chore: added payload
gentlementlegen Sep 21, 2024
ca9c2d9
chore: added payload
gentlementlegen Sep 21, 2024
041f7d0
chore: removed logs
gentlementlegen Sep 21, 2024
622f4fe
chore: added errors
gentlementlegen Sep 23, 2024
49175d7
chore: added errors
gentlementlegen Sep 23, 2024
8b0d115
chore: added errors
gentlementlegen Sep 23, 2024
c902519
chore: added errors
gentlementlegen Sep 23, 2024
7b728e7
chore: added errors
gentlementlegen Sep 23, 2024
4a1e7eb
chore: added errors
gentlementlegen Sep 23, 2024
0a4c3c2
chore: added errors
gentlementlegen Sep 23, 2024
9e57d63
chore: added errors
gentlementlegen Sep 23, 2024
481c1dc
chore: added errors
gentlementlegen Sep 23, 2024
50a2892
chore: added errors
gentlementlegen Sep 23, 2024
df5c64f
chore: added errors
gentlementlegen Sep 23, 2024
6be3f8c
chore: added configuration
gentlementlegen Sep 30, 2024
f722548
chore: added configuration
gentlementlegen Sep 30, 2024
910811d
feat: added configuration generation script
gentlementlegen Oct 2, 2024
242a671
chore: updated generated configuration
github-actions[bot] Oct 2, 2024
c6d3c97
chore: test all in actions
gentlementlegen Oct 2, 2024
59e6c56
chore: test all in actions
gentlementlegen Oct 2, 2024
09279d1
chore: test all in actions
gentlementlegen Oct 2, 2024
59ca697
chore: test all in actions
gentlementlegen Oct 2, 2024
6cb9492
chore: test all in actions
gentlementlegen Oct 2, 2024
dbf3d35
chore: test all in actions
gentlementlegen Oct 2, 2024
3326c7c
chore: test all in actions
gentlementlegen Oct 2, 2024
86f0268
chore: fixed knip
gentlementlegen Oct 2, 2024
62f5cad
chore: fixed tests
gentlementlegen Oct 2, 2024
56a8d33
chore: fixed tests
gentlementlegen Oct 2, 2024
75f2bb6
chore: fixed tests
gentlementlegen Oct 2, 2024
7417f3d
chore: removed logs
gentlementlegen Oct 2, 2024
425758a
Merge pull request #21 from gentlementlegen/feat/schema-validation
0x4007 Oct 3, 2024
cdaa46f
chore: renamed return
gentlementlegen Oct 3, 2024
b9e0cf3
chore: renamed bot commit
gentlementlegen Oct 3, 2024
e7040d2
Update compute.yml
gentlementlegen Oct 21, 2024
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
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 @@ -2,14 +2,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": "yarn@1.22.22"
}
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
Loading