Skip to content

Commit 483c7b7

Browse files
✅(tooling) require PR fields (#652)
* ✅(tooling) require PR fields * 🔧(tooling) merge chore_require_labels in new action
1 parent e1f7d07 commit 483c7b7

2 files changed

Lines changed: 118 additions & 26 deletions

File tree

.github/workflows/chore_require_labels.yml

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: PR Fields Check
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, edited, ready_for_review, labeled, unlabeled]
6+
merge_group:
7+
8+
permissions:
9+
pull-requests: read
10+
11+
jobs:
12+
check-pr-fields:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Check PR fields
16+
# GH_TOKEN must be a PAT with read:project scope to query GitHub Projects v2
17+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
18+
with:
19+
github-token: ${{ secrets.GH_TOKEN || github.token }}
20+
script: |
21+
const validLabels = new Set([
22+
'breaking', 'added', 'modified', 'removed', 'bugfix', 'dependencies', 'documentation'
23+
]);
24+
const labels = context.payload.pull_request.labels.map(l => l.name);
25+
const matching = labels.filter(l => validLabels.has(l));
26+
if (matching.length !== 1) {
27+
core.setFailed(
28+
`This pull request must have exactly one changelog label (${[...validLabels].join(', ')}). Found: ${matching.length === 0 ? 'none' : matching.join(', ')}.`
29+
);
30+
return;
31+
}
32+
33+
const { owner, repo } = context.repo;
34+
const prNumber = context.payload.pull_request.number;
35+
36+
const query = `
37+
query($owner: String!, $repo: String!, $number: Int!) {
38+
repository(owner: $owner, name: $repo) {
39+
pullRequest(number: $number) {
40+
closingIssuesReferences(first: 10) {
41+
totalCount
42+
}
43+
projectItems(first: 10) {
44+
nodes {
45+
project {
46+
title
47+
}
48+
fieldValues(first: 30) {
49+
nodes {
50+
... on ProjectV2ItemFieldSingleSelectValue {
51+
name
52+
field { ... on ProjectV2FieldCommon { name } }
53+
}
54+
... on ProjectV2ItemFieldIterationValue {
55+
title
56+
field { ... on ProjectV2FieldCommon { name } }
57+
}
58+
... on ProjectV2ItemFieldTextValue {
59+
text
60+
field { ... on ProjectV2FieldCommon { name } }
61+
}
62+
... on ProjectV2ItemFieldNumberValue {
63+
number
64+
field { ... on ProjectV2FieldCommon { name } }
65+
}
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
`;
74+
75+
const result = await github.graphql(query, { owner, repo, number: prNumber });
76+
const pr = result.repository.pullRequest;
77+
78+
const linkedIssuesCount = pr.closingIssuesReferences.totalCount;
79+
const projectItems = pr.projectItems.nodes;
80+
81+
const hasDevelopment = linkedIssuesCount > 0;
82+
const hasProjects = projectItems.length > 0;
83+
84+
if (!hasDevelopment && !hasProjects) {
85+
core.setFailed(
86+
'This pull request must have either the "Development" field (a linked issue) or be added to a "Projects" board.'
87+
);
88+
return;
89+
}
90+
91+
if (hasProjects) {
92+
const requiredFields = ['Importance', 'Size', 'Iteration', 'Epic'];
93+
const errors = [];
94+
95+
for (const item of projectItems) {
96+
const filledFields = new Set();
97+
98+
for (const fieldValue of item.fieldValues.nodes) {
99+
const fieldName = fieldValue.field?.name;
100+
if (!fieldName) continue;
101+
const value = fieldValue.name ?? fieldValue.title ?? fieldValue.text ?? fieldValue.number;
102+
if (value !== null && value !== undefined) {
103+
filledFields.add(fieldName);
104+
}
105+
}
106+
107+
const missingFields = requiredFields.filter(f => !filledFields.has(f));
108+
if (missingFields.length > 0) {
109+
errors.push(
110+
`Project "${item.project.title}" is missing required fields: ${missingFields.join(', ')}.`
111+
);
112+
}
113+
}
114+
115+
if (errors.length > 0) {
116+
core.setFailed(errors.join('\n'));
117+
}
118+
}

0 commit comments

Comments
 (0)