Skip to content

Add implementation summary document #4

Add implementation summary document

Add implementation summary document #4

Workflow file for this run

# This workflow checks for common PR submission issues
# and provides helpful feedback to contributors
name: PR Submission Checks
on:
pull_request_target:
types: [opened, reopened, synchronize]
branches:
- main
permissions:
pull-requests: write
contents: read
jobs:
check-pr-submission:
name: Check PR submission best practices
runs-on: ubuntu-latest
steps:
- name: Check if PR is from main branch of fork
id: check-main-branch
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const headRef = pr.head.ref;
const headRepo = pr.head.repo.full_name;
const baseRepo = pr.base.repo.full_name;
const isFork = headRepo !== baseRepo;
const isFromMain = headRef === 'main';
const isFromOrg = pr.head.repo.owner.type === 'Organization';
console.log(`PR #${pr.number} details:`);
console.log(` Head branch: ${headRef}`);
console.log(` Head repo: ${headRepo}`);
console.log(` Base repo: ${baseRepo}`);
console.log(` Is fork: ${isFork}`);
console.log(` From main branch: ${isFromMain}`);
console.log(` Owner type: ${pr.head.repo.owner.type}`);
core.setOutput('is_fork', isFork);
core.setOutput('is_from_main', isFromMain);
core.setOutput('is_from_org', isFromOrg);
core.setOutput('head_ref', headRef);
core.setOutput('head_repo', headRepo);
return {
isFork,
isFromMain,
isFromOrg,
needsComment: isFork && isFromMain
};
- name: Post comment about main branch submission
if: steps.check-main-branch.outputs.is_fork == 'true' && steps.check-main-branch.outputs.is_from_main == 'true'
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
// Check if we already posted this comment
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const botComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('submitted from the main branch')
);
if (botComment) {
console.log('Comment about main branch already exists, skipping');
return;
}
// Post helpful comment
const commentBody = `## ⚠️ Pull Request Submitted from Main Branch
Hi @${pr.user.login}! 👋

Check failure on line 83 in .github/workflows/pr-checks.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/pr-checks.yml

Invalid workflow file

You have an error in your yaml syntax on line 83
Thank you for your contribution to voc4cat!
We noticed that this pull request was submitted from the \`main\` branch of your fork. While this works, it can make it difficult to keep your fork synchronized with the main repository and may cause issues when making future contributions.
### Why is this problematic?
- It makes it harder to update your fork with changes from the upstream repository
- You won't be able to work on multiple pull requests simultaneously
- Future updates to the main repository may create conflicts in your fork
### How to fix this (for future PRs):
1. **Create a new branch for your changes:**
\`\`\`bash
git checkout -b descriptive-branch-name
\`\`\`
2. **Make your changes and commit them to this branch**
3. **Push the branch to your fork:**
\`\`\`bash
git push origin descriptive-branch-name
\`\`\`
4. **Create your pull request from this new branch**
### For this current PR:
You don't need to close this PR. We can still merge it! However, for your next contribution, please consider using a feature branch as described above.
For more information, see our [Contributing Guidelines](https://github.com/nfdi4cat/voc4cat/blob/main/CONTRIBUTING.md).
---
*This is an automated message to help improve the contribution workflow. If you have any questions, please don't hesitate to ask!* 🚀`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
- name: Post info about organization account
if: steps.check-main-branch.outputs.is_from_org == 'true'
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
// Check if we already posted this comment
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const botComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('organization account')
);
if (botComment) {
console.log('Comment about organization account already exists, skipping');
return;
}
const commentBody = `## ℹ️ Pull Request from Organization Account
Hi @${pr.user.login}! 👋
We noticed that this pull request comes from an organization account rather than a personal account.
### Note about organization contributions:
- This is generally fine and the PR can be merged normally
- However, if you intend to be credited as a contributor in releases (e.g., in Zenodo), we may need your personal account information
- Organization accounts may have different permission settings that could affect your ability to update the PR
### If you prefer to use a personal account:
You can transfer the PR by:
1. Forking the repository to your personal account
2. Creating a new branch with your changes
3. Submitting a new PR from your personal fork
Feel free to proceed with this PR, or let us know if you'd like to switch to a personal account.
---
*This is an automated informational message. No action is required unless you want to change accounts.* 📋`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
check-top-concepts:
name: Check for top-concept classification
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.merged }}
steps:
- name: Checkout PR branch
uses: actions/checkout@v5
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.head_ref }}
- name: Checkout main branch for comparison
uses: actions/checkout@v5
with:
ref: main
path: _main_branch
sparse-checkout: |
vocabularies/
fetch-depth: 1
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install rdflib
- name: Check for top-concept classification
id: check-concepts
run: |
python << 'PYTHON_SCRIPT'
import os
import sys
from pathlib import Path
from rdflib import Graph, Namespace, RDF, SKOS
SKOS = Namespace("http://www.w3.org/2004/02/skos/core#")
VOC4CAT = Namespace("https://w3id.org/nfdi4cat/voc4cat_")
def load_graph(vocab_dir):
"""Load all turtle files from vocabulary directory"""
g = Graph()
vocab_path = Path(vocab_dir)
if not vocab_path.exists():
return g
for ttl_file in vocab_path.rglob("*.ttl"):
try:
g.parse(ttl_file, format="turtle")
except Exception as e:
print(f"Warning: Could not parse {ttl_file}: {e}")
return g
def get_top_concepts(graph):
"""Get all concepts marked as skos:topConceptOf"""
top_concepts = set()
for s, p, o in graph.triples((None, SKOS.topConceptOf, None)):
top_concepts.add(s)
return top_concepts
def get_all_concepts(graph):
"""Get all SKOS concepts"""
concepts = set()
for s in graph.subjects(RDF.type, SKOS.Concept):
concepts.add(s)
return concepts
def get_broader_concepts(graph, concept):
"""Get broader concepts for a given concept"""
broader = set()
for o in graph.objects(concept, SKOS.broader):
broader.add(o)
# Also check inverse (narrower) relationships
for s in graph.subjects(SKOS.narrower, concept):
broader.add(s)
return broader
def is_classified_under_top_concept(graph, concept, top_concepts, visited=None):
"""Check if a concept is eventually under a top concept"""
if visited is None:
visited = set()
if concept in visited:
return False
visited.add(concept)
if concept in top_concepts:
return True
broader = get_broader_concepts(graph, concept)
for b in broader:
if is_classified_under_top_concept(graph, b, top_concepts, visited):
return True
return False
# Load graphs
print("Loading vocabulary files...")
current_graph = load_graph("vocabularies/voc4cat")
main_graph = load_graph("_main_branch/vocabularies/voc4cat")
# Get concepts
current_concepts = get_all_concepts(current_graph)
main_concepts = get_all_concepts(main_graph)
new_concepts = current_concepts - main_concepts
print(f"Found {len(current_concepts)} total concepts in PR")
print(f"Found {len(main_concepts)} concepts in main branch")
print(f"Found {len(new_concepts)} new concepts in this PR")
if len(new_concepts) == 0:
print("No new concepts added in this PR")
sys.exit(0)
# Get top concepts from current graph
top_concepts = get_top_concepts(current_graph)
print(f"Found {len(top_concepts)} top concepts")
# Check each new concept
unclassified_concepts = []
for concept in new_concepts:
if not is_classified_under_top_concept(current_graph, concept, top_concepts):
# Get the concept's label for reporting
label = None
for o in current_graph.objects(concept, SKOS.prefLabel):
label = str(o)
break
unclassified_concepts.append((str(concept), label))
if unclassified_concepts:
print(f"\n⚠️ Found {len(unclassified_concepts)} concept(s) not classified under a top concept:")
for uri, label in unclassified_concepts:
print(f" - {uri} ({label if label else 'no label'})")
# Write output for GitHub Action
with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as f:
f.write(f"has_unclassified=true\n")
# Format the list for the comment
concepts_list = '\n'.join([f"- `{uri}` {f'({label})' if label else '(no label)'}"
for uri, label in unclassified_concepts])
# Use multiline output format for GitHub Actions
f.write(f"unclassified_list<<EOF\n{concepts_list}\nEOF\n")
sys.exit(1)
else:
print("✅ All new concepts are properly classified under top concepts")
with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as f:
f.write(f"has_unclassified=false\n")
sys.exit(0)
PYTHON_SCRIPT
- name: Post comment about missing classification
if: failure() && steps.check-concepts.outputs.has_unclassified == 'true'
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const unclassifiedList = `${{ steps.check-concepts.outputs.unclassified_list }}`;
// Check if we already posted this comment
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const botComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Missing Top-Concept Classification')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: `## ⚠️ Missing Top-Concept Classification
Hi @${pr.user.login}! 👋
Our automated check found that some new concepts in this PR are not classified under one of the top-level concepts in the vocabulary hierarchy.
### Concepts needing classification:
${unclassifiedList}
### Why is this important?
Every concept in the voc4cat vocabulary should be part of the hierarchical structure, ultimately linking to one of the top-level concepts. This ensures:
- Better organization and discoverability
- Consistent vocabulary structure
- Proper semantic relationships
### How to fix this:
1. Open your vocabulary Excel file
2. For each concept listed above, add a broader concept (\`skos:broader\`) relationship
3. Ensure the chain of broader concepts eventually reaches one of the top concepts
4. Commit and push the updated file
The top-level concepts in voc4cat include concepts like:
- Process
- Method
- Material entity
- Quality
- Role
- etc.
For more guidance, see our [Contributing Guidelines](https://github.com/nfdi4cat/voc4cat/blob/main/CONTRIBUTING.md).
---
*This is an automated check. If you believe this is an error or need help, please ask!* 🔍
**Last checked:** ${new Date().toISOString()}`
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `## ⚠️ Missing Top-Concept Classification
Hi @${pr.user.login}! 👋
Our automated check found that some new concepts in this PR are not classified under one of the top-level concepts in the vocabulary hierarchy.
### Concepts needing classification:
${unclassifiedList}
### Why is this important?
Every concept in the voc4cat vocabulary should be part of the hierarchical structure, ultimately linking to one of the top-level concepts. This ensures:
- Better organization and discoverability
- Consistent vocabulary structure
- Proper semantic relationships
### How to fix this:
1. Open your vocabulary Excel file
2. For each concept listed above, add a broader concept (\`skos:broader\`) relationship
3. Ensure the chain of broader concepts eventually reaches one of the top concepts
4. Commit and push the updated file
The top-level concepts in voc4cat include concepts like:
- Process
- Method
- Material entity
- Quality
- Role
- etc.
For more guidance, see our [Contributing Guidelines](https://github.com/nfdi4cat/voc4cat/blob/main/CONTRIBUTING.md).
---
*This is an automated check. If you believe this is an error or need help, please ask!* 🔍`
});
}