Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CODEOWNERS file
# This file defines code owners for the repository
# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners

# DevOps team owns all files
* @dhwani-ris/dhwani-devops

# GitHub workflows - DevOps team
/.github/workflows/ @dhwani-ris/dhwani-devops

# Configuration files - DevOps team
/.github/ @dhwani-ris/dhwani-devops
/.releaserc @dhwani-ris/dhwani-devops

258 changes: 258 additions & 0 deletions .github/workflows/add-pr-to-devops.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
name: Add PR to DevOps Board

on:
pull_request:
types: [opened, reopened, synchronize]
branches: [main, master, develop, development]

jobs:
add_to_project:
runs-on: ubuntu-latest
if: |
github.event.pull_request.base.ref == 'main' ||
github.event.pull_request.base.ref == 'master' ||
github.event.pull_request.base.ref == 'develop' ||
github.event.pull_request.base.ref == 'development'
permissions:
contents: read
pull-requests: write
repository-projects: write
organization-projects: write
issues: read
steps:
- name: Add PR to DevOps Release Board
uses: actions/github-script@v8
# Note: GITHUB_TOKEN is automatically provided by GitHub Actions
# No need to add it as a secret - it's available in all workflows
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNodeId = context.payload.pull_request.node_id;
const prNumber = context.payload.pull_request.number;
const prUrl = context.payload.pull_request.html_url;

console.log(`Processing PR #${prNumber} (Node ID: ${prNodeId})`);

try {
// Try GraphQL API first (more reliable for projects)
let projects = [];
try {
const graphqlQuery = `
query {
organization(login: "dhwani-ris") {
projectsV2(first: 50, states: OPEN) {
nodes {
id
title
number
}
}
}
}
`;
const graphqlResponse = await github.graphql(graphqlQuery);
if (graphqlResponse?.organization?.projectsV2?.nodes) {
projects = graphqlResponse.organization.projectsV2.nodes.map(p => ({
id: p.id,
name: p.title,
number: p.number
}));
console.log(`Found ${projects.length} projects via GraphQL`);
}
} catch (graphqlError) {
console.log('GraphQL query failed, falling back to REST API:', graphqlError.message);
}

// Fallback to REST API if GraphQL didn't work
if (projects.length === 0) {
const { data: restProjects } = await github.rest.projects.listForOrg({
org: 'dhwani-ris',
state: 'open'
});
projects = restProjects;
console.log(`Found ${projects.length} projects via REST API`);
}

console.log(`Found ${projects.length} organization projects`);

if (projects.length === 0) {
console.log('⚠️ No organization projects found. Check permissions.');
return;
}

// Log all project names for debugging
console.log('Available projects:', projects.map(p => `"${p.name}" (ID: ${p.id})`).join(', '));

// Find the DevOps Release & QC Board project - try exact match first
// Project name from UI: "Dhwani – DevOps Release & QC Board" (with en-dash)
let devopsProject = projects.find(p => {
const name = p.name.toLowerCase();
// Try exact match with various dash characters (hyphen, en-dash, em-dash)
return name === 'dhwani - devops release & qc board' ||
name === 'dhwani – devops release & qc board' || // en-dash (U+2013)
name === 'dhwani — devops release & qc board' || // em-dash (U+2014)
name === 'dhwani - devops release and qc board' ||
name === 'dhwani – devops release and qc board' ||
(name.includes('dhwani') && name.includes('devops') && name.includes('release') && name.includes('qc')) ||
(name.includes('dhwani') && name.includes('devops') && name.includes('release') && name.includes('board')) ||
(name.includes('devops') && name.includes('release') && name.includes('qc')) ||
(name.includes('devops') && name.includes('release') && name.includes('board')) ||
(name.includes('devops') && name.includes('release')) ||
(name.includes('devops') && name.includes('qc'));
});

// If not found, try just "devops"
if (!devopsProject) {
devopsProject = projects.find(p =>
p.name.toLowerCase().includes('devops')
);
}

if (!devopsProject) {
console.log('❌ DevOps Release & QC Board project not found');
console.log('Available project names:', projects.map(p => `"${p.name}"`).join(', '));
console.log('Searched for: "Dhwani – DevOps Release & QC Board" (with en-dash)');
return;
}

console.log(`✅ Found project: "${devopsProject.name}" (ID: ${devopsProject.id})`);

// Check if this is a Projects V2 (new) or Projects V1 (old)
const isV2Project = devopsProject.id && typeof devopsProject.id === 'string' && devopsProject.id.startsWith('PVT_');

if (isV2Project) {
// Use GraphQL for Projects V2
console.log('Using Projects V2 (GraphQL)');

// Get project fields/columns
const projectQuery = `
query($projectId: ID!) {
node(id: $projectId) {
... on ProjectV2 {
id
title
fields(first: 20) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
views(first: 10) {
nodes {
id
name
}
}
}
}
}
`;

const projectData = await github.graphql(projectQuery, {
projectId: devopsProject.id
});

// Add item to project using GraphQL
const addItemMutation = `
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: {
projectId: $projectId
contentId: $contentId
}) {
item {
id
}
}
}
`;

try {
await github.graphql(addItemMutation, {
projectId: devopsProject.id,
contentId: prNodeId
});
console.log(`✅ Successfully added PR #${prNumber} to DevOps Release & QC Board (Projects V2)`);
console.log(` Project: "${devopsProject.name}"`);
console.log(` PR URL: ${prUrl}`);
} catch (addError) {
if (addError.message && addError.message.includes('already exists')) {
console.log(`✅ PR #${prNumber} is already in the project`);
} else {
throw addError;
}
}
} else {
// Use REST API for Projects V1
console.log('Using Projects V1 (REST API)');

// Get project columns
const { data: columns } = await github.rest.projects.listColumns({
project_id: devopsProject.id
});

if (columns.length === 0) {
console.log('❌ No columns found in project');
return;
}

console.log(`Found ${columns.length} columns:`, columns.map(c => `"${c.name}"`).join(', '));

// Find the first column (usually "To do", "In progress", or "Backlog")
const firstColumn = columns[0];
console.log(`Adding PR to column: "${firstColumn.name}"`);

// Check if PR is already in the project (check all columns)
let alreadyAdded = false;
for (const column of columns) {
const { data: cards } = await github.rest.projects.listCards({
column_id: column.id
});
alreadyAdded = cards.some(card =>
card.content_url && card.content_url.includes(`/pulls/${prNumber}`)
);
if (alreadyAdded) {
console.log(`✅ PR #${prNumber} is already in the project (column: ${column.name})`);
break;
}
}

if (!alreadyAdded) {
// Add PR to the first column
await github.rest.projects.createCard({
column_id: firstColumn.id,
content_id: prNodeId,
content_type: 'PullRequest'
});

console.log(`✅ Successfully added PR #${prNumber} to DevOps Release & QC Board`);
console.log(` Project: "${devopsProject.name}"`);
console.log(` Column: "${firstColumn.name}"`);
console.log(` PR URL: ${prUrl}`);
}
}
} catch (error) {
console.log('❌ Error adding PR to project:', error.message);
console.log('Error details:', JSON.stringify(error, null, 2));

if (error.status === 403) {
console.log('⚠️ Permission denied (403). Check repository permissions.');
} else if (error.status === 404) {
console.log('⚠️ Not found (404). Check project name.');
} else if (error.status === 401) {
console.log('⚠️ Unauthorized (401). Check authentication.');
}

// Don't fail the workflow, just log the error
console.log('⚠️ Continuing workflow despite error...');
}

69 changes: 51 additions & 18 deletions .github/workflows/auto-reviewer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Auto Request Review

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened, ready_for_review, closed]
branches: [main, master, develop, development]

permissions:
pull-requests: write
Expand All @@ -13,8 +14,7 @@ jobs:
name: Request Review from Default Reviewer
runs-on: ubuntu-latest
if: |
github.event.pull_request.base.ref == 'main' ||
github.event.pull_request.base.ref == 'master'
(github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review')

steps:
- name: Request review from default reviewer
Expand Down Expand Up @@ -46,27 +46,60 @@ jobs:

console.log('Current reviewers:', JSON.stringify(currentReviews, null, 2));

// Check if reviewer is already requested
const isAlreadyRequested = currentReviews.users?.some(
// Check if DevOps team is already requested
const devopsTeam = 'dhwani-ris/dhwani-devops';
const isTeamRequested = currentReviews.teams?.some(
team => `${team.organization.login}/${team.slug}` === devopsTeam
);

// Check if individual reviewer is already requested
const isUserRequested = currentReviews.users?.some(
user => user.login.toLowerCase() === reviewer.toLowerCase()
) || currentReviews.teams?.some(
team => team.slug.toLowerCase() === reviewer.toLowerCase()
);

if (isAlreadyRequested) {
console.log(`✅ Review already requested from ${reviewer}`);
return;
// Request review from DevOps team (from CODEOWNERS)
if (!isTeamRequested) {
try {
// Try with just team slug first
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
team_reviewers: ['dhwani-devops'],
});
console.log(`✅ Successfully requested review from ${devopsTeam}`);
} catch (teamError) {
console.log(`⚠️ Could not request review from team ${devopsTeam}:`, teamError.message);
console.log(` Error details:`, JSON.stringify(teamError, null, 2));
// Try alternative: get team ID and use GraphQL
try {
const { data: teams } = await github.rest.teams.list({
org: 'dhwani-ris',
});
const devopsTeamData = teams.find(t => t.slug === 'dhwani-devops');
if (devopsTeamData) {
console.log(` Found team ID: ${devopsTeamData.id}, trying alternative method...`);
}
} catch (e) {
console.log(` Could not fetch team info:`, e.message);
}
}
} else {
console.log(`✅ Review already requested from ${devopsTeam}`);
}

// Request review from default reviewer
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
reviewers: [reviewer],
});

console.log(`✅ Successfully requested review from ${reviewer}`);
if (!isUserRequested) {
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
reviewers: [reviewer],
});
console.log(`✅ Successfully requested review from ${reviewer}`);
} else {
console.log(`✅ Review already requested from ${reviewer}`);
}
} catch (error) {
console.error(`❌ Error requesting review from ${reviewer}:`, error);
console.error('Error details:', JSON.stringify(error, null, 2));
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/bot-handler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ on:
issue_comment:
types: [created, edited]
pull_request:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, closed]
branches: [master]

permissions:
contents: write
Expand Down
Loading
Loading