Skip to content

Commit

Permalink
23360 - Zenhub release management automation (#1756)
Browse files Browse the repository at this point in the history
  • Loading branch information
seeker25 authored Sep 18, 2024
1 parent 5d96f21 commit c4edd68
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 0 deletions.
5 changes: 5 additions & 0 deletions releases/env_example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GITHUB_ACCESS_TOKEN=
ZENHUB_GRAPHQL_TOKEN=
ZENHUB_REPOSITORY_ID=
TARGET_ZENHUB_WORKSPACE_ID=
ENTITY_GITHUB_REPO_ID=
5 changes: 5 additions & 0 deletions releases/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1.What does this do?
Creates a zenhub release with all the issues that exist in the latest pay release in github.

2. How do I get secrets?
Need GITHUB_ACCESS_TOKEN, ZENHUB_GRAPHQL_TOKEN setup through github and zenhub.
19 changes: 19 additions & 0 deletions releases/release_to_zenhub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from dotenv import load_dotenv
from util import add_issues_to_release, create_release, get_issue_id, get_issues_from_repo, get_workspace_release_for_report

load_dotenv()

auth_issue_ids, _ = get_issues_from_repo('sbc-auth')
pay_issue_ids, release_names = get_issues_from_repo('sbc-pay', latest_release_only=True)
pay_release_issue_ids = list(set([item for item in pay_issue_ids if item not in auth_issue_ids]))
target_release_name = f'Pay Release - {release_names[0]}'
release_id = get_workspace_release_for_report(f'Pay - {release_names[0]}')
if release_id is None:
release_id = create_release(target_release_name)
print(f'Zenhub release created id: {release_id} - {target_release_name}')
else:
print(f'Zenhub release found id: {release_id} - {target_release_name}')
for issue in pay_release_issue_ids:
issue_id = get_issue_id(issue)
add_issues_to_release(issue_id, release_id)
print(f'Adding issue {issue} - {issue_id} to Zenhub release {release_id}')
3 changes: 3 additions & 0 deletions releases/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dotenv
github
requests
104 changes: 104 additions & 0 deletions releases/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import os
import re
import requests
from github import Github


def get_issues_from_repo(target, latest_release_only=False):
"""Get issue ids from repos on github."""
issue_ids = []
release_names = []
g = Github(os.getenv("GITHUB_ACCESS_TOKEN"))
repository_owner = 'bcgov'
repo = g.get_repo(f"{repository_owner}/{target}")
for release in repo.get_releases():
release_names.append(release.title)
for l in release.body.splitlines():
if re.search(r'\d+ -', l):
issue_ids.append(re.search(r'\d+ -', l).group(0).replace(' -',''))
if latest_release_only:
break
return issue_ids, release_names

def add_issues_to_release(issue_id: int, zenhub_release_hash: str):
"""Add issues to Zenhub release."""
response = requests.post('https://api.zenhub.com/public/graphql',
headers={
'Authorization': f'Bearer {os.getenv("ZENHUB_GRAPHQL_TOKEN")}'
},
json={
"operationName": "addIssuesToReleases",
"query": "mutation addIssuesToReleases($AddIssuesToReleasesInput: AddIssuesToReleasesInput!) { addIssuesToReleases(input: $AddIssuesToReleasesInput) { releases { id title state startOn endOn closedAt __typename } __typename }}}",
"variables": {"AddIssuesToReleasesInput":{"issueIds":[issue_id],"releaseIds":[zenhub_release_hash]}},
}, timeout=60000)
assert response.status_code == 200

def get_issue_id(issue_number):
"""Get issue from Zenhub."""
query = "query getGHIssueFull($repositoryId: ID!, $issueNumber: Int!, $workspaceId: ID!) { issueByInfo(repositoryId: $repositoryId, issueNumber: $issueNumber) { id title body state type number viewerPermission created_at: createdAt updated_at: updatedAt html_url: htmlUrl user { id avatar_url: avatarUrl login __typename } labels { nodes { id color name __typename } __typename } assignees { totalCount nodes { id login avatar_url: avatarUrl name __typename } __typename } repository { id name estimateSet { values __typename } __typename } epic { id __typename } estimate { value __typename } parentEpics { nodes { id issue { title __typename } __typename } __typename } parentZenhubEpics { nodes { id __typename } __typename } milestone { ...activeMilestone __typename } releases { nodes { id title __typename } __typename } pipelineIssues { nodes { id workspace { _id: id name displayName isEditable pipelinesConnection { nodes { _id: id name __typename } __typename } prioritiesConnection { nodes { _id: id name color __typename } __typename } __typename } pipeline { _id: id __typename } priority { id __typename } __typename } __typename } sprints(workspaceId: $workspaceId) { nodes { id __typename } __typename } ...issueDependencies __typename }}fragment activeMilestone on Milestone { ...milestoneData __typename}fragment milestoneData on Milestone { __typename}fragment issueDependencies on Issue { id type viewerPermission blockedItems { totalCount nodes { ... on Issue { id title issueState: state number type htmlUrl repository { id name owner { id login __typename } __typename } pipelineIssue(workspaceId: $workspaceId) { id pipeline { id name __typename } priority { id name __typename } __typename } __typename } ... on ZenhubEpic { id title state __typename } __typename } __typename } blockingItems { totalCount nodes { ... on Issue { id title issueState: state number type htmlUrl repository { id name owner { id login __typename } __typename } pipelineIssue(workspaceId: $workspaceId) { id pipeline { id name __typename } priority { id name __typename } __typename } __typename } ... on ZenhubEpic { id title state __typename } __typename } __typename } __typename}"
response = requests.post('https://api.zenhub.com/public/graphql',
headers={
'Authorization': f'Bearer {os.getenv("ZENHUB_GRAPHQL_TOKEN")}'
},
json={
"operationName": "getGHIssueFull",
"query": query,
"variables": {
"repositoryId": os.getenv("ZENHUB_REPOSITORY_ID"),
"issueNumber": int(issue_number),
"workspaceId": os.getenv("TARGET_ZENHUB_WORKSPACE_ID")
}
}, timeout=60000)
assert response.status_code == 200
data = response.json()
return data.get('data').get('issueByInfo').get('id')

def get_workspace_release_for_report(release_name):
"""Get workspace release for report."""
response = requests.post('https://api.zenhub.com/public/graphql',
headers={
'Authorization': f'Bearer {os.getenv("ZENHUB_GRAPHQL_TOKEN")}'
},
json={
"operationName": "getWorkspaceReleasesForReport",
"variables": {
"workspaceId": os.getenv("TARGET_ZENHUB_WORKSPACE_ID"),
"pageSize": 30,
"state": {
"eq": "OPEN"
},
"query": f'{release_name}'
},
"query": "query getWorkspaceReleasesForReport($workspaceId: ID!, $pageSize: Int, $after: String, $query: String, $state: ReleaseStateInput, $repositoryIds: [ID!], $ids: [ID!]) { workspace(id: $workspaceId) { id releases( first: $pageSize query: $query after: $after state: $state repositoryIds: $repositoryIds ids: $ids ) { totalCount pageInfo { hasNextPage hasPreviousPage endCursor __typename } nodes { ...ReleaseForReport __typename } __typename } __typename }}fragment ReleaseForReport on Release { id title state endOn closedAt startOn description issuesCount __typename}"
}, timeout=60000)
assert response.status_code == 200
data = response.json()
nodes = data.get('data').get('workspace').get('releases').get('nodes')
if nodes:
return nodes[0].get('id')
return None

def create_release(release_name):
"""Create release in Zenhub."""
response = requests.post('https://api.zenhub.com/public/graphql',
headers={
'Authorization': f'Bearer {os.getenv("ZENHUB_GRAPHQL_TOKEN")}'
},
json={
"operationName": "createRelease",
"variables": {
"input": {
"release": {
"title": release_name,
"description": release_name,
"startOn": "2024-01-01T00:00:00.000Z",
"endOn": "2024-01-02T00:00:00.000Z",
"repositoryGhIds": [int(os.getenv('ENTITY_GITHUB_REPO_ID'))]
}
}
},
"query": "mutation createRelease($input: CreateReleaseInput!) { createRelease(input: $input) { release { id title startOn endOn __typename } __typename }}"
}, timeout=60000)
assert response.status_code == 200
data = response.json()
return data.get('data').get('createRelease').get('release').get('id')

0 comments on commit c4edd68

Please sign in to comment.