Skip to content

Commit

Permalink
New option for adding Synced label to GitHub issues after sync with J…
Browse files Browse the repository at this point in the history
…ira (#59)

* Added options for labeling GitHub issue with customizable 'synced-with-jira' 
* Actions from labeling issues with synced-to-jira are ignored
  • Loading branch information
lorenzo-medici authored Dec 19, 2024
1 parent 03574a8 commit 1614ff2
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 4 deletions.
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ sequenceDiagram
App->>Jira: Searches for existing Jira issues
Jira->>App: Provides list of existing issues if any
App->>Jira: Creates/updates Jira issue
App->>repo: (Optional) Adds 'synced-to-jira' on the Issue
App->>repo: (Optional) Adds a comment on the Issue
App->>GH: Returns web response
Expand Down Expand Up @@ -54,4 +55,4 @@ sequenceDiagram
App1->>repo: (Optional) Adds a comment on the Issue
App1->>GH: Returns web response
```
```
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ settings:

# (Optional) (Default: false) Add a new comment in GitHub with a link to Jira created issue
add_gh_comment: false

# (Optional) (Default: false) Add a 'synced-to-jira' label to newly created issues once a
# corresponding ticket is successfully created in Jira.
# This label serves as confirmation that the issue sync process was completed successfully.
add_gh_synced_label: false

# (Optional) (Default: true) Synchronize issue description from GitHub to Jira
sync_description: true
Expand Down
20 changes: 17 additions & 3 deletions github_jira_sync_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
> This message was autogenerated
"""

gh_synced_label_name = "synced-to-jira"


def define_logger():
"""Define logger to output to the file and to STDOUT."""
Expand Down Expand Up @@ -187,6 +189,15 @@ async def bot(request: Request, payload: dict = Body(...)):
)
}

if payload["action"] == "labeled":
if payload["label"]["name"] == gh_synced_label_name:
return {
"msg": (
f"Action was triggered by Issue being labeled with {gh_synced_label_name}."
" Purposefully ignored as caused by this bot."
)
}

owner = payload["repository"]["owner"]["login"]
repo_name = payload["repository"]["name"]

Expand Down Expand Up @@ -293,10 +304,13 @@ async def bot(request: Request, payload: dict = Body(...)):
new_issue = jira.create_issue(fields=issue_dict)
existing_issues.append(new_issue)

if settings.get("add_gh_synced_label", False):
gh_issue.add_to_labels(gh_synced_label_name)

if settings["add_gh_comment"]:
gh_issue.create_comment(
gh_comment_body_template.format(jira_issue_link=new_issue.permalink())
)
gh_comment_body = gh_comment_body_template.format(jira_issue_link=new_issue.permalink())

gh_issue.create_comment(gh_comment_body)

# need this since we allow to sync issue on many actions. And if someone commented
# we first create a Jira issue, then create a comment
Expand Down
1 change: 1 addition & 0 deletions github_jira_sync_app/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ settings:
components:
labels:
add_gh_comment: false
add_gh_synced_label: false
sync_description: true
sync_comments: true
epic_key:
Expand Down
210 changes: 210 additions & 0 deletions tests/unit/payloads/issue_labeled_synced_to_jira.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
{
"action": "labeled",
"issue": {
"url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/30",
"repository_url": "https://api.github.com/repos/beliaev-maksim/test-ci",
"labels_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/30/labels{/name}",
"comments_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/30/comments",
"events_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/30/events",
"html_url": "https://github.com/beliaev-maksim/test-ci/issues/30",
"id": 1745060534,
"node_id": "I_kwDOI-HsRM5oA4K2",
"number": 30,
"title": "day after",
"user": {
"login": "beliaev-maksim",
"id": 51964909,
"node_id": "MDQ6VXNlcjUxOTY0OTA5",
"avatar_url": "https://avatars.githubusercontent.com/u/51964909?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/beliaev-maksim",
"html_url": "https://github.com/beliaev-maksim",
"followers_url": "https://api.github.com/users/beliaev-maksim/followers",
"following_url": "https://api.github.com/users/beliaev-maksim/following{/other_user}",
"gists_url": "https://api.github.com/users/beliaev-maksim/gists{/gist_id}",
"starred_url": "https://api.github.com/users/beliaev-maksim/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/beliaev-maksim/subscriptions",
"organizations_url": "https://api.github.com/users/beliaev-maksim/orgs",
"repos_url": "https://api.github.com/users/beliaev-maksim/repos",
"events_url": "https://api.github.com/users/beliaev-maksim/events{/privacy}",
"received_events_url": "https://api.github.com/users/beliaev-maksim/received_events",
"type": "User",
"site_admin": false
},
"labels": [
{
"id": 5157325446,
"node_id": "LA_kwDOI-HsRM8AAAABM2aKhg",
"url": "https://api.github.com/repos/beliaev-maksim/test-ci/labels/bug",
"name": "bug",
"color": "d73a4a",
"default": true,
"description": "Something isn't working"
}
],
"state": "open",
"locked": false,
"assignee": null,
"assignees": [

],
"milestone": null,
"comments": 0,
"created_at": "2023-06-07T05:19:14Z",
"updated_at": "2023-06-07T05:19:14Z",
"closed_at": null,
"author_association": "OWNER",
"active_lock_reason": null,
"body": null,
"reactions": {
"url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/30/reactions",
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"hooray": 0,
"confused": 0,
"heart": 0,
"rocket": 0,
"eyes": 0
},
"timeline_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/30/timeline",
"performed_via_github_app": null,
"state_reason": null
},
"label": {
"id": 5157325446,
"node_id": "LA_kwDOI-HsRM8AAAABM2aKhg",
"url": "https://api.github.com/repos/beliaev-maksim/test-ci/labels/synced-to-jira",
"name": "synced-to-jira",
"color": "d73a4a",
"default": false,
"description": "Ticket in Jira has been created for this issue"
},
"repository": {
"id": 602008644,
"node_id": "R_kgDOI-HsRA",
"name": "test-ci",
"full_name": "beliaev-maksim/test-ci",
"private": false,
"owner": {
"login": "beliaev-maksim",
"id": 51964909,
"node_id": "MDQ6VXNlcjUxOTY0OTA5",
"avatar_url": "https://avatars.githubusercontent.com/u/51964909?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/beliaev-maksim",
"html_url": "https://github.com/beliaev-maksim",
"followers_url": "https://api.github.com/users/beliaev-maksim/followers",
"following_url": "https://api.github.com/users/beliaev-maksim/following{/other_user}",
"gists_url": "https://api.github.com/users/beliaev-maksim/gists{/gist_id}",
"starred_url": "https://api.github.com/users/beliaev-maksim/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/beliaev-maksim/subscriptions",
"organizations_url": "https://api.github.com/users/beliaev-maksim/orgs",
"repos_url": "https://api.github.com/users/beliaev-maksim/repos",
"events_url": "https://api.github.com/users/beliaev-maksim/events{/privacy}",
"received_events_url": "https://api.github.com/users/beliaev-maksim/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/beliaev-maksim/test-ci",
"description": null,
"fork": false,
"url": "https://api.github.com/repos/beliaev-maksim/test-ci",
"forks_url": "https://api.github.com/repos/beliaev-maksim/test-ci/forks",
"keys_url": "https://api.github.com/repos/beliaev-maksim/test-ci/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/beliaev-maksim/test-ci/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/beliaev-maksim/test-ci/teams",
"hooks_url": "https://api.github.com/repos/beliaev-maksim/test-ci/hooks",
"issue_events_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/events{/number}",
"events_url": "https://api.github.com/repos/beliaev-maksim/test-ci/events",
"assignees_url": "https://api.github.com/repos/beliaev-maksim/test-ci/assignees{/user}",
"branches_url": "https://api.github.com/repos/beliaev-maksim/test-ci/branches{/branch}",
"tags_url": "https://api.github.com/repos/beliaev-maksim/test-ci/tags",
"blobs_url": "https://api.github.com/repos/beliaev-maksim/test-ci/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/beliaev-maksim/test-ci/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/beliaev-maksim/test-ci/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/beliaev-maksim/test-ci/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/beliaev-maksim/test-ci/statuses/{sha}",
"languages_url": "https://api.github.com/repos/beliaev-maksim/test-ci/languages",
"stargazers_url": "https://api.github.com/repos/beliaev-maksim/test-ci/stargazers",
"contributors_url": "https://api.github.com/repos/beliaev-maksim/test-ci/contributors",
"subscribers_url": "https://api.github.com/repos/beliaev-maksim/test-ci/subscribers",
"subscription_url": "https://api.github.com/repos/beliaev-maksim/test-ci/subscription",
"commits_url": "https://api.github.com/repos/beliaev-maksim/test-ci/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/beliaev-maksim/test-ci/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/beliaev-maksim/test-ci/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/beliaev-maksim/test-ci/contents/{+path}",
"compare_url": "https://api.github.com/repos/beliaev-maksim/test-ci/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/beliaev-maksim/test-ci/merges",
"archive_url": "https://api.github.com/repos/beliaev-maksim/test-ci/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/beliaev-maksim/test-ci/downloads",
"issues_url": "https://api.github.com/repos/beliaev-maksim/test-ci/issues{/number}",
"pulls_url": "https://api.github.com/repos/beliaev-maksim/test-ci/pulls{/number}",
"milestones_url": "https://api.github.com/repos/beliaev-maksim/test-ci/milestones{/number}",
"notifications_url": "https://api.github.com/repos/beliaev-maksim/test-ci/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/beliaev-maksim/test-ci/labels{/name}",
"releases_url": "https://api.github.com/repos/beliaev-maksim/test-ci/releases{/id}",
"deployments_url": "https://api.github.com/repos/beliaev-maksim/test-ci/deployments",
"created_at": "2023-02-15T09:57:53Z",
"updated_at": "2023-05-15T09:25:41Z",
"pushed_at": "2023-05-15T09:29:16Z",
"git_url": "git://github.com/beliaev-maksim/test-ci.git",
"ssh_url": "[email protected]:beliaev-maksim/test-ci.git",
"clone_url": "https://github.com/beliaev-maksim/test-ci.git",
"svn_url": "https://github.com/beliaev-maksim/test-ci",
"homepage": null,
"size": 10,
"stargazers_count": 0,
"watchers_count": 0,
"language": "Python",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 2,
"license": null,
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"topics": [

],
"visibility": "public",
"forks": 0,
"open_issues": 2,
"watchers": 0,
"default_branch": "master"
},
"sender": {
"login": "beliaev-maksim",
"id": 51964909,
"node_id": "MDQ6VXNlcjUxOTY0OTA5",
"avatar_url": "https://avatars.githubusercontent.com/u/51964909?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/beliaev-maksim",
"html_url": "https://github.com/beliaev-maksim",
"followers_url": "https://api.github.com/users/beliaev-maksim/followers",
"following_url": "https://api.github.com/users/beliaev-maksim/following{/other_user}",
"gists_url": "https://api.github.com/users/beliaev-maksim/gists{/gist_id}",
"starred_url": "https://api.github.com/users/beliaev-maksim/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/beliaev-maksim/subscriptions",
"organizations_url": "https://api.github.com/users/beliaev-maksim/orgs",
"repos_url": "https://api.github.com/users/beliaev-maksim/repos",
"events_url": "https://api.github.com/users/beliaev-maksim/events{/privacy}",
"received_events_url": "https://api.github.com/users/beliaev-maksim/received_events",
"type": "User",
"site_admin": false
},
"installation": {
"id": 35534068,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU1MzQwNjg="
}
}
47 changes: 47 additions & 0 deletions tests/unit/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,50 @@ def test_issue_closed_as_not_planned(signature_mock):

assert response.status_code == 200
assert response.json() == {"msg": "Closed existing Jira Issue as not planned"}


@responses.activate(assert_all_requests_are_fired=True)
def test_issue_created_and_synced_label(signature_mock):
"""Test when a bug is created on GitHub with the right label and <add_gh_synced_label> is set
Tests the following scenario:
1. Authenticate in GitHub
2. Get issue from GitHub
3. Get content of .jira_sync_config.yaml from GitHub repo
4. Ensure that the issue on GitHub is label with the approved label
5. Authenticate in Jira
6. Validate via JQL that this issue does not exist in Jira
7. Create new issue in Jira
8. Add synced-to-jira label to the issue. If it doesn't exist it is created
automatically by GitHub and then added
9. The new webhook sent by GitHub for labeling the Issue with synced-to-jira
is immediately ignored
"""

responses._add_from_file(UNITTESTS_DIR / "url_responses" / "github_auth.yaml")
responses._add_from_file(
UNITTESTS_DIR / "url_responses" / "github_settings_with_gh_sync_label.yaml"
)
responses._add_from_file(
UNITTESTS_DIR / "url_responses" / "github_responses_add_synced_label.yaml"
)
responses._add_from_file(UNITTESTS_DIR / "url_responses" / "jira_jql_no_issues.yaml")
responses._add_from_file(UNITTESTS_DIR / "url_responses" / "jira_auth_responses.yaml")
responses._add_from_file(UNITTESTS_DIR / "url_responses" / "jira_create_issue.yaml")
response = client.post(
"/",
json=_get_json("issue_created_without_label.json"),
)

assert response.status_code == 200
assert response.json() == {"msg": "Issue was created in Jira. "}

response = client.post("/", json=_get_json("issue_labeled_synced_to_jira.json"))

assert response.status_code == 200
assert response.json() == {
"msg": (
"Action was triggered by Issue being labeled with synced-to-jira. "
"Purposefully ignored as caused by this bot."
)
}
10 changes: 10 additions & 0 deletions tests/unit/url_responses/github_responses_add_synced_label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# this file is generated automatically via responses. Use _recorder.record()
responses:
# add the synced-to-jira label to the new issue
- response:
auto_calculate_content_length: false
body: '[{"id":208045946,"node_id":"MDU6TGFiZWwyMDgwNDU5NDY=","url":"https://api.github.com:443/repos/beliaev-maksim/test-ci/labels/synced-to-jira","name":"synced-to-jira","description":"Ticket in Jira has been created for this issue","color":"f29513","default":false}]'
content_type: text/plain
method: POST
status: 200
url: https://api.github.com:443/repos/beliaev-maksim/test-ci/issues/30/labels
28 changes: 28 additions & 0 deletions tests/unit/url_responses/github_settings_with_gh_sync_label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# get content of .jira_sync_config.yaml to load the settings for the repo
responses:
- response:
auto_calculate_content_length: false
body: '{"name":".jira_sync_config.yaml","path":".github/.jira_sync_config.yaml","sha":"a1a8aab6de92fc3eed4211350200ada4a2405b9c","size":297,"url":"https://api.github.com/repos/beliaev-maksim/test-ci/contents/.github/.jira_sync_config.yaml?ref=master","html_url":"https://github.com/beliaev-maksim/test-ci/blob/master/.github/.jira_sync_config.yaml","git_url":"https://api.github.com/repos/beliaev-maksim/test-ci/git/blobs/a1a8aab6de92fc3eed4211350200ada4a2405b9c","download_url":"https://raw.githubusercontent.com/beliaev-maksim/test-ci/master/.github/.jira_sync_config.yaml","type":"file","content":"c2V0dGluZ3M6CiAgY29tcG9uZW50czoKICAgIC0gSW9UCiAgICAtIERBQ0ggVFQKICBsYWJlbHM6CiAgYWRkX2doX2NvbW1lbnQ6IGZhbHNlCiAgYWRkX2doX3N5bmNlZF9sYWJlbDogdHJ1ZQogIHN5bmNfZGVzY3JpcHRpb246IHRydWUKICBzeW5jX2NvbW1lbnRzOiB0cnVlCiAgZXBpY19rZXk6ICJNVEMtMjk2IgogIGppcmFfcHJvamVjdF9rZXk6ICJNVEMiCiAgc3RhdHVzX21hcHBpbmc6CiAgICBvcGVuZWQ6IFVudHJpYWdlZAogICAgY2xvc2VkOiBkb25lCiAgbGFiZWxfbWFwcGluZzoKICAgIGVuaGFuY2VtZW50OiBTdG9yeQo=","encoding":"base64","_links":{"self":"https://api.github.com/repos/beliaev-maksim/test-ci/contents/.github/.jira_sync_config.yaml?ref=master","git":"https://api.github.com/repos/beliaev-maksim/test-ci/git/blobs/a1a8aab6de92fc3eed4211350200ada4a2405b9c","html":"https://github.com/beliaev-maksim/test-ci/blob/master/.github/.jira_sync_config.yaml"}}'
content_type: text/plain
method: GET
status: 200
url: https://api.github.com:443/repos/beliaev-maksim/test-ci/contents/.github/.jira_sync_config.yaml


# That is the base64 encoded content
#settings:
# components:
# - IoT
# - DACH TT
# labels:
# add_gh_comment: false
# add_gh_synced_label: true
# sync_description: true
# sync_comments: true
# epic_key: "MTC-296"
# jira_project_key: "MTC"
# status_mapping:
# opened: Untriaged
# closed: done
# label_mapping:
# enhancement: Story

0 comments on commit 1614ff2

Please sign in to comment.