Create work item in Azure DevOps when a GitHub Issue is created
Update Azure DevOps work item when a GitHub Issue is updated
The id of the Work Item created or updated
This action uses graphQL to get the Sprint and Story Points values from the project the issue is assigned to. It relies on the fields to have the following names:
- Story Points: "Story"
- Iteration / Sprint: "Sprint"
If a different name is used change the "name" field in "fieldValueByName" for Sprint or Story as needed.
Action is triggered on the following:
- issues that are opened, edited, deleted, closed, reopened, labeled, unlabeled, or assigned
- issue_comments that are created, edited, or deleted
You'll need the following to setup the action:
- GraphQL: A functional understanding of the new GraphQL calls used by GitHub is recommended.
- GitHub API Docs: GitHub GraphQL API Docs
- GitHub Graph QL Explorer: GitHub GraphQL API Explorer
- Add a secret named
ADO_PERSONAL_ACCESS_TOKEN
containing an Azure Personal Access Token with "read & write" permission for Work Items - Organization secrets named
ADO_GH_SYNC_APP_ID
andADO_GH_SYNC_PRIVATE_KEY
set with theApp ID
andPrivate Key
forADO GH Issue Sync
GitHub App. - Install the Azure Boards App from the GitHub Marketplace. To enable the app for a repository follow these steps:
- Navigate to the organization the repository is in.
- Click on "Settings" across the banner below the organization name.
- Click on "GitHub Apps" in the menu on the left.
- Click on "Configure" for the Azure Boards applicaiton.
- Click "Save" towards the bottom the page.
- Sign in with your Microsoft account.
- From the menu select the project you want to connect. You can use the search feature to narrow down the choices.
- Click "Continue"
- Pick the correct Azure DevOps repository to add the Azure Boards feature.
- Add a workflow like the example below.
- Note the global env values. Those are there to make the ado-sync vairables easier to set.
- Name of the ADO Organization
azure_org_name
- Name of the Azure DevOps Project Board to send the GitHub issues to.
azure_project_name
- What Azure DevOps "Area" the tickets should be assigned to by default (if needed)
azure_area_subname
- ADO Work Order ID to assign "Related Work" (if needed)
azure_parent_id
All other ${{ *** }} entries are contexts generated by GitHub during the run.
name: GitHub Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
issue_comment:
types:
[created, edited, deleted]
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
azure_org_name: "GeneralMills"
azure_project_name: "Cloverleaf"
azure_area_subname: "Cloud Platform"
azure_parent_id: "642868"
app_id: "${{ secrets.ADO_GH_SYNC_APP_ID }}"
app_private_key: "${{ secrets.ADO_GH_SYNC_PRIVATE_KEY }}"
jobs:
get-issue-info:
runs-on: gcp
container: docker.generalmills.com/k8s-ghcli:44-dea02e4
name: GitHub Issue Sync
outputs:
gh_iteration: ${{ steps.get-issue-info.outputs.iteration }}
gh_story_points: ${{ steps.get-issue-info.outputs.story_points }}
gh_current_sprint: ${{ steps.get-issue-info.outputs.current_sprint }}
gh_assignee: ${{ steps.get-issue-info.outputs.assignee }}
steps:
- name: Get GitHub App Auth
id: gh_app_auth
uses: peter-murray/workflow-application-token-action@v2
with:
application_id: "${{ secrets.ADO_GH_SYNC_APP_ID }}"
application_private_key: "${{ secrets.ADO_GH_SYNC_PRIVATE_KEY }}"
organization: "${{ github.repository_owner }}"
- name: Run GraphQL Queries
id: get-issue-info
env:
GITHUB_TOKEN: ${{ steps.gh_app_auth.outputs.token }}
run: |
gh api graphql -f query='query get_iteration_title ($owner:String!, $repo:String!, $gh_issue_number:Int!) {
repository(name: $repo, owner: $owner) {
issue(number: $gh_issue_number) {
title
projectItems(first: 1) {
nodes {
sprint: fieldValueByName(name: "Sprint") {
... on ProjectV2ItemFieldIterationValue {
title
}
}
story: fieldValueByName(name: "Story") {
... on ProjectV2ItemFieldNumberValue {
number
}
}
}
}
assignees (first: 1) {
nodes {
login
}
}
}
}
}' -F owner=${{ github.repository_owner }} -F repo=${{ github.event.repository.name }} -F gh_issue_number=${{ github.event.issue.number }} > result.json
if [ $(jq -r '.data.repository.issue.assignees.nodes[0].login' result.json) = "null" ]
then
github_assignee="null null"
else
github_assignee=$(jq -r '.data.repository.issue.assignees.nodes[0].login' result.json)
echo "GitHub Assignee: $github_assignee"
gh api graphql -f query='query get_assignee ($owner:String!,$assignee:String!){
organization(login: $owner) {
samlIdentityProvider {
externalIdentities(first: 1, login: $assignee) {
edges {
node {
samlIdentity {
givenName
familyName
}
}
}
}
}
}
}' -F owner=${{ github.repository_owner }} -F assignee="$github_assignee" > name.json
first=$(jq -r '.data.organization.samlIdentityProvider.externalIdentities.edges[0].node.samlIdentity.givenName' name.json)
last=$(jq -r '.data.organization.samlIdentityProvider.externalIdentities.edges[0].node.samlIdentity.familyName' name.json)
echo "assignee=$first $last" >> $GITHUB_ENV
echo "GitHub Assignee Name: $first $last"
fi
curl -u :"${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}" 'https://dev.azure.com/${{ env.azure_org_name }}/${{ env.azure_project_name }}/_apis/work/teamsettings/iterations?$timeframe=current&api-version=6.0' > currentsprint.json
echo "iteration=$(jq -r '.data.repository.issue.projectItems.nodes[0].sprint.title' result.json)" >> $GITHUB_ENV
echo "story_points=$(jq -r '.data.repository.issue.projectItems.nodes[0].story.number' result.json)" >> $GITHUB_ENV
echo "current_sprint=$(jq -r '.value[0].name' currentsprint.json)" >> $GITHUB_ENV
echo $(jq -r '.data.repository.issue.assignees.nodes[0].login' result.json)
- name: Synch GH Issue to ADO
id: ado-sync
uses: GeneralMills/github-actions-issue-to-work-item@master
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ steps.gh_app_auth.outputs.token }}"
ado_organization: "${{ env.azure_org_name }}"
ado_project: "${{ env.azure_project_name }}"
ado_area_path: "${{ env.azure_project_name }}\\${{ env.azure_area_subname }}"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_parent: "${{ env.azure_parent_id }}"
ado_current_sprint: "${{ env.azure_project_name }}\\${{ env.current_sprint }}"
ado_iteration: "${{ env.azure_project_name }}\\${{ env.sprint }}"
ado_story_points: "${{ env.story_points }}"
ado_assignee: "${{ env.assignee }}"
ado_bypassrules: true
log_level: 400
azure_org_name
: The top level (root) organization in ADO.azure_project_name
: The name of the project in ADO.azure_area_subname
: The subname to complete the iteration area path in ADO AKA "Area"azure_parent_id
: The parent work item to nest the ADO entry into. AKA "Related Work"
ado_organization
: Set from theazure_org_name
Global ENV.ado_project
: Set from theazure_project_name
Global ENV.ado_area_path
: Built from combining theazure_project_name
andazure_area_subname
Global ENVado_wit
: The type of work item you want the ADO entry to have. "User Story" is the typical default.ado_new_state
: When a new work item is created in ADO, use this state. "New" is defaultado_active_state
: When a work item is reopened in ADO, use this state. "Active" is default.ado_close_state
: When closing a work item, use this state in ADO. "Closed" is default.ado_parent
: "Set from theazure_parent_id
Global ENV.ado_current_sprint
: Built from theazure_project_name
and the current sprint in ADO. Used to create a value if the sprint is not set in the GitHub Issue. The current ADO sprint is pulled using a REST API call to the ADO Project's iteration for the team.ado_iteration
: Built from theazure_project_name
and a GraphQL query finds the sprint name and automatically assigns it. If it was not, then theado_current_sprint
is used in it's place.ado_story_points
: The points set in the GitHub issue. Default is 1.ado_assignee
: The first assignee listed in the issue. If there is none listed the work item in ADO will be listed as "Unassigned" by default. Requires the "admin:org" permission to lookup SAML login details. This provides a workaround to the email address on an individual's GitHub account not set to a General Mills email address.ado_bypassrules
: Used to bypass any rules on the form to ensure the work item gets created in Azure DevOps. However, some organizations getting bypassrules permissions for the token owner can go against policy. By default the bypassrules will be set to false. If you have rules on your form that prevent the work item to be created with just Title and Description, then you will need to set to true.log_level
: Used to set the logging verbosity to help with debugging in a production environment. 100 is the default, 400 is the max.