Skip to content

Commit 840718e

Browse files
authored
Add actions to automatically update issues and tasks for the database boards (baserow#4113)
1 parent 109acd2 commit 840718e

File tree

3 files changed

+556
-0
lines changed

3 files changed

+556
-0
lines changed

.github/workflows/README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# GitHub Workflows
2+
3+
## Database Team PR Automation
4+
5+
**File:** `database-projects-pr-workflow.yml`
6+
7+
This workflow automatically updates the Database Team's GitHub Project board based on PR events.
8+
9+
### How it works
10+
11+
The workflow triggers on PR events (opened, review requested, review submitted, merged, etc.) and automatically:
12+
13+
1. **Checks domain labels** - Only processes PRs with labels starting with `domain::database` or `domain::core`
14+
2. **Adds PR to project** - Ensures the PR is added to the Database Team project board
15+
3. **Updates Status field** - Sets the PR status based on its state:
16+
- `In Progress` - PR is a draft
17+
- `In Review` - PR is ready for review
18+
- `Done` - PR has been merged
19+
4. **Updates Review Status field** - Sets the review status based on review state:
20+
- `Awaiting` - Set when:
21+
- PR switches from draft to ready for review
22+
- There are pending review requests
23+
- A reviewer who requested changes has been re-requested for review
24+
- No reviewers have been requested yet and no approvals exist
25+
- `Feedback` - Changes have been requested by a reviewer
26+
- `Merge` - No pending review requests and at least one approval exists
27+
5. **Updates linked issues** - Also updates the Status field (not Review Status) of any issues linked to the PR. This assumes a 1:1 relation between PRs and issues.
28+
29+
### State transitions
30+
31+
```
32+
Draft PR opened/converted to draft
33+
→ Status: In Progress
34+
→ Review Status: (cleared)
35+
36+
PR ready for review / review requested
37+
→ Status: In Review
38+
→ Review Status: Awaiting
39+
40+
Changes requested
41+
→ Status: In Review
42+
→ Review Status: Feedback
43+
44+
Changes requested reviewer re-requested for review
45+
→ Status: In Review
46+
→ Review Status: Awaiting
47+
48+
PR approved
49+
→ Status: In Review
50+
→ Review Status: Merge
51+
52+
PR merged
53+
→ Status: Done
54+
→ Review Status: (cleared)
55+
```
56+
57+
### Configuration
58+
59+
The workflow uses these environment variables (defined at the top of the file):
60+
61+
| Variable | Description |
62+
|----------|-------------|
63+
| `PROJECT_NUMBER` | The GitHub Project number (currently `3`) |
64+
| `DOMAIN_LABELS` | Labels that trigger the workflow (`domain::database`, `domain::core`) |
65+
| `STATUS_FIELD_NAME` | Name of the Status field in the project |
66+
| `REVIEW_STATUS_FIELD_NAME` | Name of the Review Status field in the project |
67+
68+
### Requirements
69+
70+
- A GitHub token with project write permissions stored as `DATABASE_PROJECT_WORKFLOW_TOKEN` secret
71+
- The project must have `Status` and `Review Status` single-select fields with the expected options
72+
73+
### Manual trigger
74+
75+
You can manually trigger the workflow for a specific PR using the "Run workflow" button in the Actions tab, providing the PR number.
76+
77+
---
78+
79+
## Database Team Issue Automation
80+
81+
**File:** `database-projects-issues-workflow.yml`
82+
83+
This workflow automatically adds new issues to the Database Team's GitHub Project board.
84+
85+
### How it works
86+
87+
The workflow triggers when an issue is opened or labeled, and:
88+
89+
1. **Checks domain labels** - Only processes issues with labels starting with `domain::database` or `domain::core`
90+
2. **Checks if already in project** - Skips if the issue is already on the project board
91+
3. **Adds issue to project** - Adds the issue to the Database Team project board
92+
4. **Sets Status to Todo** - Sets the initial status to `Todo`
93+
94+
### Configuration
95+
96+
| Variable | Description |
97+
|----------|-------------|
98+
| `PROJECT_NUMBER` | The GitHub Project number (currently `3`) |
99+
| `DOMAIN_LABELS` | Labels that trigger the workflow (`domain::database`, `domain::core`) |
100+
| `STATUS_FIELD_NAME` | Name of the Status field in the project |
101+
| `STATUS_TODO` | The initial status value for new issues (`Todo`) |
102+
103+
### Requirements
104+
105+
- A GitHub token with project write permissions stored as `DATABASE_PROJECT_WORKFLOW_TOKEN` secret
106+
- The project must have a `Status` single-select field with a `Todo` option
107+
108+
### Manual trigger
109+
110+
You can manually trigger the workflow for a specific issue using the "Run workflow" button in the Actions tab, providing the issue number.
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
name: Update Database Team Project Fields
2+
3+
env:
4+
PROJECT_NUMBER: '3'
5+
DOMAIN_LABELS: '["domain::database", "domain::core"]'
6+
STATUS_FIELD_NAME: 'Status'
7+
STATUS_TODO: 'Todo'
8+
9+
on:
10+
issues:
11+
types: [labeled, opened]
12+
workflow_dispatch:
13+
inputs:
14+
issue_number:
15+
description: 'Issue number to process'
16+
required: true
17+
type: number
18+
19+
jobs:
20+
update-project-fields:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Add Issue to Project
24+
uses: actions/github-script@v7
25+
with:
26+
github-token: ${{ secrets.DATABASE_PROJECT_WORKFLOW_TOKEN }}
27+
script: |
28+
const issueNumber = context.eventName === 'workflow_dispatch'
29+
? ${{ github.event.inputs.issue_number || 0 }}
30+
: context.payload.issue.number;
31+
32+
console.log(`Processing Issue #${issueNumber} (event: ${context.eventName}/${context.payload.action || 'manual'})`);
33+
34+
// ============================================================
35+
// FETCH ALL DATA WITH SINGLE GRAPHQL QUERY
36+
// ============================================================
37+
38+
const data = await github.graphql(`
39+
query($owner: String!, $repo: String!, $issue: Int!, $projectNumber: Int!) {
40+
organization(login: $owner) {
41+
projectV2(number: $projectNumber) {
42+
id
43+
fields(first: 20) {
44+
nodes {
45+
... on ProjectV2SingleSelectField {
46+
id
47+
name
48+
options { id, name }
49+
}
50+
}
51+
}
52+
}
53+
}
54+
repository(owner: $owner, name: $repo) {
55+
issue(number: $issue) {
56+
id
57+
labels(first: 20) {
58+
nodes { name }
59+
}
60+
projectItems(first: 10) {
61+
nodes {
62+
id
63+
project { id }
64+
}
65+
}
66+
}
67+
}
68+
}
69+
`, {
70+
owner: context.repo.owner,
71+
repo: context.repo.repo,
72+
issue: issueNumber,
73+
projectNumber: parseInt('${{ env.PROJECT_NUMBER }}')
74+
});
75+
76+
const project = data.organization.projectV2;
77+
const issue = data.repository.issue;
78+
79+
// ============================================================
80+
// CHECK DOMAIN LABELS (early exit if not relevant)
81+
// ============================================================
82+
83+
const labels = issue.labels.nodes.map(l => l.name);
84+
const domainLabels = ${{ env.DOMAIN_LABELS }};
85+
const hasDomainLabel = labels.some(label =>
86+
domainLabels.some(domain => label.startsWith(domain))
87+
);
88+
89+
if (!hasDomainLabel) {
90+
console.log(`Issue #${issueNumber} has no domain label (${labels.join(', ') || 'none'}), skipping`);
91+
return;
92+
}
93+
94+
console.log(`Issue has domain label: ${labels.filter(l => l.startsWith('domain::')).join(', ')}`);
95+
96+
// ============================================================
97+
// CHECK IF ALREADY IN PROJECT
98+
// ============================================================
99+
100+
const existingItem = issue.projectItems.nodes.find(
101+
item => item.project.id === project.id
102+
);
103+
104+
if (existingItem) {
105+
console.log('Issue is already in the project');
106+
return;
107+
}
108+
109+
// ============================================================
110+
// ADD ISSUE TO PROJECT
111+
// ============================================================
112+
113+
const addResult = await github.graphql(`
114+
mutation($projectId: ID!, $contentId: ID!) {
115+
addProjectV2ItemById(input: {
116+
projectId: $projectId
117+
contentId: $contentId
118+
}) {
119+
item {
120+
id
121+
}
122+
}
123+
}
124+
`, {
125+
projectId: project.id,
126+
contentId: issue.id
127+
});
128+
129+
const itemId = addResult.addProjectV2ItemById.item.id;
130+
console.log(`Issue added to project (item: ${itemId})`);
131+
132+
// ============================================================
133+
// SET STATUS TO TODO
134+
// ============================================================
135+
136+
const statusField = project.fields.nodes.find(f => f.name === '${{ env.STATUS_FIELD_NAME }}');
137+
const todoOption = statusField?.options.find(o => o.name === '${{ env.STATUS_TODO }}');
138+
139+
if (!statusField || !todoOption) {
140+
console.log('Warning: Status field or Todo option not found');
141+
return;
142+
}
143+
144+
await github.graphql(`
145+
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {
146+
updateProjectV2ItemFieldValue(input: {
147+
projectId: $projectId
148+
itemId: $itemId
149+
fieldId: $fieldId
150+
value: $value
151+
}) {
152+
projectV2Item { id }
153+
}
154+
}
155+
`, {
156+
projectId: project.id,
157+
itemId,
158+
fieldId: statusField.id,
159+
value: { singleSelectOptionId: todoOption.id }
160+
});
161+
162+
console.log(`Status → ${{ env.STATUS_TODO }}`);
163+
console.log('Completed!');

0 commit comments

Comments
 (0)