✨(web) add 2FA authentication on Django admin #148
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Fields Check | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, edited, ready_for_review, labeled, unlabeled] | |
| merge_group: | |
| permissions: | |
| pull-requests: read | |
| jobs: | |
| check-pr-fields: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check PR fields | |
| # GH_TOKEN must be a PAT with read:project scope to query GitHub Projects v2 | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN || github.token }} | |
| script: | | |
| const validLabels = new Set([ | |
| 'breaking', 'added', 'modified', 'removed', 'bugfix', 'dependencies', 'documentation' | |
| ]); | |
| const labels = context.payload.pull_request.labels.map(l => l.name); | |
| const matching = labels.filter(l => validLabels.has(l)); | |
| if (matching.length !== 1) { | |
| core.setFailed( | |
| `This pull request must have exactly one changelog label (${[...validLabels].join(', ')}). Found: ${matching.length === 0 ? 'none' : matching.join(', ')}.` | |
| ); | |
| return; | |
| } | |
| const { owner, repo } = context.repo; | |
| const prNumber = context.payload.pull_request.number; | |
| const query = ` | |
| query($owner: String!, $repo: String!, $number: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $number) { | |
| closingIssuesReferences(first: 10) { | |
| totalCount | |
| } | |
| projectItems(first: 10) { | |
| nodes { | |
| project { | |
| title | |
| } | |
| fieldValues(first: 30) { | |
| nodes { | |
| ... on ProjectV2ItemFieldSingleSelectValue { | |
| name | |
| field { ... on ProjectV2FieldCommon { name } } | |
| } | |
| ... on ProjectV2ItemFieldIterationValue { | |
| title | |
| field { ... on ProjectV2FieldCommon { name } } | |
| } | |
| ... on ProjectV2ItemFieldTextValue { | |
| text | |
| field { ... on ProjectV2FieldCommon { name } } | |
| } | |
| ... on ProjectV2ItemFieldNumberValue { | |
| number | |
| field { ... on ProjectV2FieldCommon { name } } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| const result = await github.graphql(query, { owner, repo, number: prNumber }); | |
| const pr = result.repository.pullRequest; | |
| const linkedIssuesCount = pr.closingIssuesReferences.totalCount; | |
| const projectItems = pr.projectItems.nodes; | |
| const hasDevelopment = linkedIssuesCount > 0; | |
| const hasProjects = projectItems.length > 0; | |
| if (!hasDevelopment && !hasProjects) { | |
| core.setFailed( | |
| 'This pull request must have either the "Development" field (a linked issue) or be added to a "Projects" board.' | |
| ); | |
| return; | |
| } | |
| if (hasProjects) { | |
| const requiredFields = ['Importance', 'Size', 'Iteration', 'Epic']; | |
| const errors = []; | |
| for (const item of projectItems) { | |
| const filledFields = new Set(); | |
| for (const fieldValue of item.fieldValues.nodes) { | |
| const fieldName = fieldValue.field?.name; | |
| if (!fieldName) continue; | |
| const value = fieldValue.name ?? fieldValue.title ?? fieldValue.text ?? fieldValue.number; | |
| if (value !== null && value !== undefined) { | |
| filledFields.add(fieldName); | |
| } | |
| } | |
| const missingFields = requiredFields.filter(f => !filledFields.has(f)); | |
| if (missingFields.length > 0) { | |
| errors.push( | |
| `Project "${item.project.title}" is missing required fields: ${missingFields.join(', ')}.` | |
| ); | |
| } | |
| } | |
| if (errors.length > 0) { | |
| core.setFailed(errors.join('\n')); | |
| } | |
| } |