Skip to content

Commit 581a1e2

Browse files
authored
Created base action (#1)
Created the core action to output the fellows GitHub handle. This action is intended to be used with other actions that needs it (like [`paritytech/auto-merge-bot`](https://github.com/paritytech/auto-merge-bot) in the runtimes repository).
1 parent 7a6cdce commit 581a1e2

File tree

10 files changed

+678
-64
lines changed

10 files changed

+678
-64
lines changed

README.md

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,47 @@
1-
# Parity GitHub Action template
1+
# Get Fellows Action
22

3-
Template used to generate GitHub Actions.
3+
GitHub action to fetch all the fellows information.
44

5-
## To start
5+
This action fetches all the fellows from the [on-chain data](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama-rpc.polkadot.io#/fellowship) and output two objects with the fellows data and with the users’ GitHub information.
66

7-
- Remember to modify the `action.yml` file to have your required attributes and details.
8-
- You can use [GitHub Action Brandings cheatsheet](https://github.com/haya14busa/github-action-brandings) to set the style of the action.
9-
- Remember to modify the name in the `package.json`.
7+
It looks in the fellows data for a field named `GitHub` and it extracts the handle from there.
8+
9+
This action is intended to be chained with other actions that requires the fellows GitHub handles.
10+
11+
## Installation
12+
This action is intended to be chained with other actions. It does not have any requirements.
13+
14+
We simply need to add the step:
15+
```yaml
16+
- uses: paritytech/get-fellows-action
17+
id: fellows
18+
```
19+
20+
A working example where we print all the names is the following:
21+
```yaml
22+
name: Mention fellows
23+
24+
on: [push]
25+
26+
jobs:
27+
mention-fellows:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: paritytech/get-fellows-action
31+
id: fellows
32+
- name: Mention them
33+
run: |
34+
echo "The fellows are $FELLOWS"
35+
env:
36+
# the handles of the fellows separated by commas
37+
FELLOWS: ${{ steps.fellows.outputs.github-handles }}"
38+
```
39+
40+
### Outputs
41+
You can find all the outputs in the [`action.yml` file](./action.yml).
42+
43+
They are:
44+
- `fellows`: A JSON array with objects of type "address": "1e2345...", "rank": rank (as a number), “githubHandle”: “handle (can be null)”
45+
- Example: `[{"address":"1e2345...",rank: 4, "githubHandle":"user-1"},{"address":"1e4532...",rank: 3, "githubHandle":"user-2"},{"address":"1e8335...",rank: 6}]`
46+
- `github-handles`: All the fellows handles separated by commas
47+
- Example: `user-1,user-2,user-3`

action.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
name: "Example Action"
2-
description: "This values need to be changed"
1+
name: "get-fellows-action"
2+
description: "Fetch all the GitHub handles from the Fellows"
33
author: Bullrich
44
branding:
5-
icon: copy
6-
color: yellow
7-
inputs:
8-
GITHUB_TOKEN:
9-
required: true
10-
description: The token to access the repo
5+
icon: users
6+
color: red
117
outputs:
12-
repo:
13-
description: 'The name of the repo in owner/repo pattern'
8+
fellows-handles:
9+
description: 'A JSON array with objects of type { "address": "1e2345...", "rank": rank (as a number), githubHandle: "can be null" }'
10+
github-handles:
11+
description: "All the fellows' Github handles separated by commas"
1412

1513
runs:
1614
using: 'docker'

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
module.exports = {
33
preset: "ts-jest",
44
testEnvironment: "node",
5+
testTimeout: 20_000,
56
testMatch: [__dirname + "/src/**/test/**/*.ts"],
67
};

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "parity-action-template",
2+
"name": "get-fellows-action",
33
"version": "0.0.1",
4-
"description": "GitHub action template for Parity",
4+
"description": "Fetch all the GitHub handles from the Fellows",
55
"main": "src/index.ts",
66
"scripts": {
77
"start": "node dist",
@@ -23,13 +23,15 @@
2323
"dependencies": {
2424
"@actions/core": "^1.10.1",
2525
"@actions/github": "^5.1.1",
26-
"@octokit/webhooks-types": "^7.3.1"
26+
"@octokit/webhooks-types": "^7.3.1",
27+
"@polkadot/api": "^10.9.1"
2728
},
2829
"devDependencies": {
2930
"@eng-automation/js-style": "^2.3.0",
3031
"@types/jest": "^29.5.5",
3132
"@vercel/ncc": "^0.38.0",
3233
"jest": "^29.7.0",
34+
"jest-mock-extended": "^3.0.5",
3335
"ts-jest": "^29.1.1",
3436
"typescript": "^5.2.2"
3537
}

src/fellows.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment */
2+
import { ApiPromise, WsProvider } from "@polkadot/api";
3+
4+
import { ActionLogger } from "./github/types";
5+
6+
type FellowData = { address: string; rank: number };
7+
8+
export type FellowObject = {
9+
address: string;
10+
githubHandle?: string;
11+
rank: number;
12+
};
13+
14+
export const fetchAllFellows = async (
15+
logger: ActionLogger,
16+
): Promise<FellowObject[]> => {
17+
let api: ApiPromise;
18+
logger.debug("Connecting to collective parachain");
19+
// we connect to the collective rpc node
20+
const wsProvider = new WsProvider(
21+
"wss://polkadot-collectives-rpc.polkadot.io",
22+
);
23+
api = await ApiPromise.create({ provider: wsProvider });
24+
try {
25+
// We fetch all the members
26+
const membersObj = await api.query.fellowshipCollective.members.entries();
27+
28+
// We iterate over the fellow data and convert them into usable values
29+
const fellows: FellowData[] = [];
30+
for (const [key, rank] of membersObj) {
31+
// @ts-ignore
32+
const [address]: [string] = key.toHuman();
33+
fellows.push({ address, ...(rank.toHuman() as object) } as FellowData);
34+
}
35+
logger.debug(JSON.stringify(fellows));
36+
37+
// Once we obtained this information, we disconnect this api.
38+
await api.disconnect();
39+
40+
logger.debug("Connecting to relay parachain.");
41+
// We connect to the relay chain
42+
api = await ApiPromise.create({
43+
provider: new WsProvider("wss://rpc.polkadot.io"),
44+
});
45+
46+
// We iterate over the different members and extract their data
47+
const users: FellowObject[] = [];
48+
for (const fellow of fellows) {
49+
logger.debug(
50+
`Fetching identity of '${fellow.address}', rank: ${fellow.rank}`,
51+
);
52+
const fellowData = (
53+
await api.query.identity.identityOf(fellow.address)
54+
).toHuman() as Record<string, unknown> | undefined;
55+
56+
let data: FellowObject = { address: fellow.address, rank: fellow.rank };
57+
58+
// If the identity is null, we ignore it.
59+
if (!fellowData) {
60+
logger.debug("Identity is null. Skipping");
61+
continue;
62+
}
63+
64+
// @ts-ignore
65+
const additional = fellowData.info.additional as
66+
| [{ Raw: string }, { Raw: string }][]
67+
| undefined;
68+
69+
// If it does not have additional data (GitHub handle goes here) we ignore it
70+
if (!additional || additional.length < 1) {
71+
logger.debug("Additional data is null. Skipping");
72+
continue;
73+
}
74+
75+
for (const additionalData of additional) {
76+
const [key, value] = additionalData;
77+
// We verify that they have an additional data of the key "github"
78+
// If it has a handle defined, we push it into the array
79+
if (
80+
key?.Raw &&
81+
key?.Raw === "github" &&
82+
value?.Raw &&
83+
value?.Raw.length > 0
84+
) {
85+
logger.debug(`Found handles: '${value.Raw}'`);
86+
// We add it to the array and remove the @ if they add it to the handle
87+
data = { ...data, githubHandle: value.Raw.replace("@", "") };
88+
}
89+
}
90+
users.push(data);
91+
}
92+
93+
logger.info(`Found users: ${JSON.stringify(Array.from(users.entries()))}`);
94+
95+
return users;
96+
} catch (error) {
97+
logger.error(error as Error);
98+
throw error;
99+
} finally {
100+
await api.disconnect();
101+
}
102+
};

src/github/pullRequest.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/index.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,17 @@
1-
import { getInput, info, setOutput } from "@actions/core";
2-
import { context, getOctokit } from "@actions/github";
3-
import { Context } from "@actions/github/lib/context";
4-
import { PullRequest } from "@octokit/webhooks-types";
1+
import { setFailed, setOutput } from "@actions/core";
52

6-
import { PullRequestApi } from "./github/pullRequest";
3+
import { FellowObject, fetchAllFellows } from "./fellows";
74
import { generateCoreLogger } from "./util";
85

9-
const getRepo = (ctx: Context) => {
10-
let repo = getInput("repo", { required: false });
11-
if (!repo) {
12-
repo = ctx.repo.repo;
13-
}
6+
const logger = generateCoreLogger();
147

15-
let owner = getInput("owner", { required: false });
16-
if (!owner) {
17-
owner = ctx.repo.owner;
18-
}
19-
20-
return { repo, owner };
8+
const mapFellows = (fellows: FellowObject[]) => {
9+
setOutput("fellows", JSON.stringify(fellows));
10+
const githubHandles = fellows
11+
.map((f) => f.githubHandle)
12+
.filter((f) => !!f)
13+
.join(",");
14+
setOutput("github-handles", githubHandles);
2115
};
2216

23-
const repo = getRepo(context);
24-
25-
setOutput("repo", `${repo.owner}/${repo.repo}`);
26-
27-
if (context.payload.pull_request) {
28-
const token = getInput("GITHUB_TOKEN", { required: true });
29-
const api = new PullRequestApi(getOctokit(token), generateCoreLogger());
30-
const author = api.getPrAuthor(context.payload.pull_request as PullRequest);
31-
info("Author of the PR is " + author);
32-
}
17+
fetchAllFellows(logger).then(mapFellows).catch(setFailed);

src/test/fellows.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { mock, MockProxy } from "jest-mock-extended";
2+
3+
import { fetchAllFellows } from "../fellows";
4+
import { ActionLogger } from "../github/types";
5+
6+
describe("Fellows test", () => {
7+
let logger: MockProxy<ActionLogger>;
8+
9+
beforeEach(() => {
10+
logger = mock<ActionLogger>();
11+
});
12+
13+
test("Should fetch fellows", async () => {
14+
const members = await fetchAllFellows(logger);
15+
expect(members.length).toBeGreaterThan(0);
16+
});
17+
});

src/test/index.test.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)