Skip to content

Commit 46c4697

Browse files
author
Danny McCormick
authored
Get it working (#2)
* Get it working (#1) * Get it working * Required token * Logging * Debug * Debug * Correct logging * No setNeutral * debug * debug * debug * debug * debug * debug * debug * debug * debug * debug * debug * working * logging * logging * logging * logging * logging * logging * logging * logging * logging * logging * debug * debug * Logging * Dont validate issues against prs * Inputs should be snake cased * Add example usage * Respond to some feedback, some still left * Respond to rest of feedback * Fix period
1 parent a10eefa commit 46c4697

File tree

8 files changed

+310
-36
lines changed

8 files changed

+310
-36
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1-
# Container Action Template
1+
# First Interaction
22

3-
To get started, click the `Use this template` button on this repository [which will create a new repository based on this template](https://github.blog/2019-06-06-generate-new-repositories-with-repository-templates/).
3+
An action for filtering pull requests and issues from first-time contributors.
4+
5+
# Usage
6+
7+
See [action.yml](action.yml)
8+
9+
```yaml
10+
steps:
11+
- uses: actions/first-interaction@v1
12+
with:
13+
repo-token: ${{ secrets.GITHUB_TOKEN }}
14+
issue-message: '# Mesage with markdown.\nThis is the message that will be displayed on users' first issue.'
15+
pr-message: 'Message that will be displayed on users' first pr. Look, a `code block` for markdown.'
16+
```
17+
18+
# License
19+
20+
The scripts and documentation in this project are released under the [MIT License](LICENSE)

__tests__/main.test.ts

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

action.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
name: 'Container Action Template'
1+
name: 'First interaction'
22
description: 'Get started with Container actions'
33
author: 'GitHub'
4-
inputs:
5-
myInput:
6-
description: 'Input to use'
7-
default: 'world'
4+
inputs:
5+
repo-token:
6+
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
7+
required: true
8+
issue-message:
9+
description: 'Comment to post on an individuals first issue'
10+
pr-message:
11+
description: 'Comment to post on an individuals first pull request'
812
runs:
913
using: 'docker'
1014
image: 'Dockerfile'

jest.config.js

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

lib/main.js

Lines changed: 121 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,135 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
77
step((generator = generator.apply(thisArg, _arguments || [])).next());
88
});
99
};
10-
const core = require('@actions/core');
11-
const github = require('@actions/github');
10+
var __importStar = (this && this.__importStar) || function (mod) {
11+
if (mod && mod.__esModule) return mod;
12+
var result = {};
13+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
14+
result["default"] = mod;
15+
return result;
16+
};
17+
Object.defineProperty(exports, "__esModule", { value: true });
18+
const core = __importStar(require("@actions/core"));
19+
const github = __importStar(require("@actions/github"));
1220
function run() {
1321
return __awaiter(this, void 0, void 0, function* () {
1422
try {
15-
const myInput = core.getInput('myInput');
16-
core.debug(`Hello ${myInput} from inside a container`);
17-
// Get github context data
23+
const issueMessage = core.getInput('issue-message');
24+
const prMessage = core.getInput('pr-message');
25+
if (!issueMessage && !prMessage) {
26+
throw new Error('Action must have at least one of issue-message or pr-message set');
27+
}
28+
// Get client and context
29+
const client = new github.GitHub(core.getInput('repo-token', { required: true }));
1830
const context = github.context;
19-
console.log(`We can even get context data, like the repo: ${context.repo.repo}`);
31+
if (context.payload.action !== 'opened') {
32+
console.log('No issue or PR was opened, skipping');
33+
return;
34+
}
35+
// Do nothing if its not a pr or issue
36+
const isIssue = !!context.payload.issue;
37+
if (!isIssue && !context.payload.pull_request) {
38+
console.log('The event that triggered this action was not a pull request or issue, skipping.');
39+
return;
40+
}
41+
// Do nothing if its not their first contribution
42+
console.log('Checking if its the users first contribution');
43+
if (!context.payload.sender) {
44+
throw new Error('Internal error, no sender provided by GitHub');
45+
}
46+
const sender = context.payload.sender.login;
47+
const issue = context.issue;
48+
let firstContribution = false;
49+
if (isIssue) {
50+
firstContribution = yield isFirstIssue(client, issue.owner, issue.repo, sender, issue.number);
51+
}
52+
else {
53+
firstContribution = yield isFirstPull(client, issue.owner, issue.repo, sender, issue.number);
54+
}
55+
if (!firstContribution) {
56+
console.log('Not the users first contribution');
57+
return;
58+
}
59+
// Do nothing if no message set for this type of contribution
60+
const message = isIssue ? issueMessage : prMessage;
61+
if (!message) {
62+
console.log('No message provided for this type of contribution');
63+
return;
64+
}
65+
const issueType = isIssue ? 'issue' : 'pull request';
66+
// Add a comment to the appropriate place
67+
console.log(`Adding message: ${message} to ${issueType} ${issue.number}`);
68+
if (isIssue) {
69+
yield client.issues.createComment({
70+
owner: issue.owner,
71+
repo: issue.repo,
72+
issue_number: issue.number,
73+
body: message
74+
});
75+
}
76+
else {
77+
yield client.pulls.createReview({
78+
owner: issue.owner,
79+
repo: issue.repo,
80+
pull_number: issue.number,
81+
body: message,
82+
event: 'COMMENT'
83+
});
84+
}
2085
}
2186
catch (error) {
2287
core.setFailed(error.message);
88+
return;
89+
}
90+
});
91+
}
92+
function isFirstIssue(client, owner, repo, sender, curIssueNumber) {
93+
return __awaiter(this, void 0, void 0, function* () {
94+
const { status, data: issues } = yield client.issues.listForRepo({
95+
owner: owner,
96+
repo: repo,
97+
creator: sender,
98+
state: 'all'
99+
});
100+
if (status !== 200) {
101+
throw new Error(`Received unexpected API status code ${status}`);
102+
}
103+
if (issues.length === 0) {
104+
return true;
105+
}
106+
for (const issue of issues) {
107+
if (issue.number < curIssueNumber && !issue.pull_request) {
108+
return false;
109+
}
110+
}
111+
return true;
112+
});
113+
}
114+
// No way to filter pulls by creator
115+
function isFirstPull(client, owner, repo, sender, curPullNumber, page = 1) {
116+
return __awaiter(this, void 0, void 0, function* () {
117+
// Provide console output if we loop for a while.
118+
console.log('Checking...');
119+
const { status, data: pulls } = yield client.pulls.list({
120+
owner: owner,
121+
repo: repo,
122+
per_page: 100,
123+
page: page,
124+
state: 'all'
125+
});
126+
if (status !== 200) {
127+
throw new Error(`Received unexpected API status code ${status}`);
128+
}
129+
if (pulls.length === 0) {
130+
return true;
131+
}
132+
for (const pull of pulls) {
133+
const login = pull.user.login;
134+
if (login === sender && pull.number < curPullNumber) {
135+
return false;
136+
}
23137
}
138+
return yield isFirstPull(client, owner, repo, sender, curPullNumber, page + 1);
24139
});
25140
}
26141
run();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
"homepage": "https://github.com/actions/container-toolkit-template#readme",
2727
"dependencies": {
2828
"@actions/core": "file:toolkit/actions-core-0.0.0.tgz",
29-
"@actions/io": "file:toolkit/actions-io-0.0.0.tgz",
3029
"@actions/exec": "file:toolkit/actions-exec-0.0.0.tgz",
3130
"@actions/github": "file:toolkit/actions-github-0.0.0.tgz",
31+
"@actions/io": "file:toolkit/actions-io-0.0.0.tgz",
3232
"@actions/tool-cache": "file:toolkit/actions-tool-cache-0.0.0.tgz"
3333
},
3434
"devDependencies": {

src/main.ts

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,170 @@
1-
const core = require('@actions/core');
2-
const github = require('@actions/github');
1+
import * as core from '@actions/core';
2+
import * as github from '@actions/github';
33

44
async function run() {
55
try {
6-
const myInput = core.getInput('myInput');
7-
core.debug(`Hello ${myInput} from inside a container`);
8-
9-
// Get github context data
6+
const issueMessage: string = core.getInput('issue-message');
7+
const prMessage: string = core.getInput('pr-message');
8+
if (!issueMessage && !prMessage) {
9+
throw new Error(
10+
'Action must have at least one of issue-message or pr-message set'
11+
);
12+
}
13+
// Get client and context
14+
const client: github.GitHub = new github.GitHub(
15+
core.getInput('repo-token', {required: true})
16+
);
1017
const context = github.context;
11-
console.log(`We can even get context data, like the repo: ${context.repo.repo}`)
18+
19+
if (context.payload.action !== 'opened') {
20+
console.log('No issue or PR was opened, skipping');
21+
return;
22+
}
23+
24+
// Do nothing if its not a pr or issue
25+
const isIssue: boolean = !!context.payload.issue;
26+
if (!isIssue && !context.payload.pull_request) {
27+
console.log(
28+
'The event that triggered this action was not a pull request or issue, skipping.'
29+
);
30+
return;
31+
}
32+
33+
// Do nothing if its not their first contribution
34+
console.log('Checking if its the users first contribution');
35+
if (!context.payload.sender) {
36+
throw new Error('Internal error, no sender provided by GitHub');
37+
}
38+
const sender: string = context.payload.sender!.login;
39+
const issue: {owner: string; repo: string; number: number} = context.issue;
40+
let firstContribution: boolean = false;
41+
if (isIssue) {
42+
firstContribution = await isFirstIssue(
43+
client,
44+
issue.owner,
45+
issue.repo,
46+
sender,
47+
issue.number
48+
);
49+
} else {
50+
firstContribution = await isFirstPull(
51+
client,
52+
issue.owner,
53+
issue.repo,
54+
sender,
55+
issue.number
56+
);
57+
}
58+
if (!firstContribution) {
59+
console.log('Not the users first contribution');
60+
return;
61+
}
62+
63+
// Do nothing if no message set for this type of contribution
64+
const message: string = isIssue ? issueMessage : prMessage;
65+
if (!message) {
66+
console.log('No message provided for this type of contribution');
67+
return;
68+
}
69+
70+
const issueType: string = isIssue ? 'issue' : 'pull request';
71+
// Add a comment to the appropriate place
72+
console.log(`Adding message: ${message} to ${issueType} ${issue.number}`);
73+
if (isIssue) {
74+
await client.issues.createComment({
75+
owner: issue.owner,
76+
repo: issue.repo,
77+
issue_number: issue.number,
78+
body: message
79+
});
80+
} else {
81+
await client.pulls.createReview({
82+
owner: issue.owner,
83+
repo: issue.repo,
84+
pull_number: issue.number,
85+
body: message,
86+
event: 'COMMENT'
87+
});
88+
}
1289
} catch (error) {
1390
core.setFailed(error.message);
91+
return;
92+
}
93+
}
94+
95+
async function isFirstIssue(
96+
client: github.GitHub,
97+
owner: string,
98+
repo: string,
99+
sender: string,
100+
curIssueNumber: number
101+
): Promise<boolean> {
102+
const {status, data: issues} = await client.issues.listForRepo({
103+
owner: owner,
104+
repo: repo,
105+
creator: sender,
106+
state: 'all'
107+
});
108+
109+
if (status !== 200) {
110+
throw new Error(`Received unexpected API status code ${status}`);
111+
}
112+
113+
if (issues.length === 0) {
114+
return true;
14115
}
116+
117+
for (const issue of issues) {
118+
if (issue.number < curIssueNumber && !issue.pull_request) {
119+
return false;
120+
}
121+
}
122+
123+
return true;
124+
}
125+
126+
// No way to filter pulls by creator
127+
async function isFirstPull(
128+
client: github.GitHub,
129+
owner: string,
130+
repo: string,
131+
sender: string,
132+
curPullNumber: number,
133+
page: number = 1
134+
): Promise<boolean> {
135+
// Provide console output if we loop for a while.
136+
console.log('Checking...');
137+
const {status, data: pulls} = await client.pulls.list({
138+
owner: owner,
139+
repo: repo,
140+
per_page: 100,
141+
page: page,
142+
state: 'all'
143+
});
144+
145+
if (status !== 200) {
146+
throw new Error(`Received unexpected API status code ${status}`);
147+
}
148+
149+
if (pulls.length === 0) {
150+
return true;
151+
}
152+
153+
for (const pull of pulls) {
154+
const login: string = pull.user.login;
155+
if (login === sender && pull.number < curPullNumber) {
156+
return false;
157+
}
158+
}
159+
160+
return await isFirstPull(
161+
client,
162+
owner,
163+
repo,
164+
sender,
165+
curPullNumber,
166+
page + 1
167+
);
15168
}
16169

17170
run();

toolkit/actions-github-0.0.0.tgz

28 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)