-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add reusable GitHub Action to automate Jira Story and Sub-task … #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| name: Create Jira Issue | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| story_summary: | ||
| required: true | ||
| type: string | ||
| story_description: | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| subtask_summary: | ||
| required: true | ||
| type: string | ||
| subtask_description: | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| environment: | ||
| required: true | ||
| type: string | ||
| secrets: | ||
| MIGRATION_BACKLOG_JIRA_EMAIL: | ||
| required: true | ||
| MIGRATION_BACKLOG_JIRA_TOKEN: | ||
| required: true | ||
| MIGRATION_BACKLOG_JIRA_URL: | ||
| required: true | ||
| MIGRATION_BACKLOG_JIRA_PROJECT: | ||
| required: true | ||
| MIGRATION_BACKLOG_JIRA_EPIC_KEY: | ||
| required: true | ||
| MIGRATION_BACKLOG_JIRA_EPIC_LINK_FIELD: | ||
| required: true | ||
|
|
||
| jobs: | ||
| create_jira_issue: | ||
| environment: | ||
| name: ${{ inputs.environment }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout actions-hub repo | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: nelc/actions-hub | ||
| ref: and/jira_automation_action | ||
| path: actions-hub | ||
| - name: Debug input and env values | ||
| run: | | ||
| echo "Story Summary: ${{ inputs.story_summary }}" | ||
| echo "Subtask Summary: ${{ inputs.subtask_summary }}" | ||
| echo "Project: ${{ secrets.MIGRATION_BACKLOG_JIRA_PROJECT }}" | ||
| echo "Epic Key: ${{ secrets.MIGRATION_BACKLOG_JIRA_EPIC_KEY }}" | ||
| echo "Epic Field: ${{ secrets.MIGRATION_BACKLOG_JIRA_EPIC_LINK_FIELD }}" | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.11" | ||
|
|
||
| - name: Install dependencies | ||
| run: pip install jira | ||
|
|
||
| - name: Run Jira issue script | ||
| run: | | ||
| python actions-hub/scripts/jira_issue_flow.py \ | ||
| --story-summary "${{ inputs.story_summary }}" \ | ||
| --story-description "${{ inputs.story_description }}" \ | ||
| --subtask-summary "${{ inputs.subtask_summary }}" \ | ||
| --subtask-description "${{ inputs.subtask_description }}" | ||
| env: | ||
| MIGRATION_BACKLOG_JIRA_EMAIL: ${{ secrets.MIGRATION_BACKLOG_JIRA_EMAIL }} | ||
| MIGRATION_BACKLOG_JIRA_TOKEN: ${{ secrets.MIGRATION_BACKLOG_JIRA_TOKEN }} | ||
| MIGRATION_BACKLOG_JIRA_URL: ${{ secrets.MIGRATION_BACKLOG_JIRA_URL }} | ||
| MIGRATION_BACKLOG_JIRA_PROJECT: ${{ secrets.MIGRATION_BACKLOG_JIRA_PROJECT }} | ||
| MIGRATION_BACKLOG_JIRA_EPIC_KEY: ${{ secrets.MIGRATION_BACKLOG_JIRA_EPIC_KEY }} | ||
| MIGRATION_BACKLOG_JIRA_EPIC_LINK_FIELD: ${{ secrets.MIGRATION_BACKLOG_JIRA_EPIC_LINK_FIELD }} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,3 +32,6 @@ yarn-error.log* | |
| # System Files | ||
| .DS_Store | ||
| Thumbs.db | ||
|
|
||
| ## virtual environments | ||
| venv | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| import argparse | ||
| import os | ||
| from functools import lru_cache | ||
| from typing import Optional | ||
|
|
||
| from jira import JIRA | ||
| from jira.resources import Issue | ||
|
|
||
|
|
||
| @lru_cache(maxsize=1) | ||
| def get_jira_client() -> JIRA: | ||
| """ | ||
| Returns a cached Jira client instance authenticated with the environment variables. | ||
|
|
||
| Returns: | ||
| JiraClient: An authenticated JIRA client instance. | ||
|
|
||
| Raises: | ||
| KeyError: If required environment variables are missing. | ||
| """ | ||
| email = os.environ["MIGRATION_BACKLOG_JIRA_EMAIL"] | ||
| token = os.environ["MIGRATION_BACKLOG_JIRA_TOKEN"] | ||
| url = os.environ["MIGRATION_BACKLOG_JIRA_URL"] | ||
|
|
||
| return JIRA(server=url, basic_auth=(email, token)) | ||
|
|
||
|
|
||
| def get_jira_issue_by_jql(jql: str, summary: str) -> Optional[Issue]: | ||
| """ | ||
| Searches for a Jira issue using a JQL query with an exact summary match. | ||
|
|
||
| Args: | ||
| jql (str): The JQL query string. | ||
| summary (str): The exact summary of the issue to find. | ||
|
|
||
| Returns: | ||
| Optional[Issue]: The matching Jira issue, or None if not found. | ||
|
|
||
| Raises: | ||
| KeyError: If required environment variables are missing. | ||
| """ | ||
| issues = get_jira_client().search_issues(jql, maxResults=10) | ||
| for issue in issues: | ||
| if issue.fields.summary == summary: | ||
| return issue | ||
|
|
||
| return None | ||
|
|
||
|
|
||
| def create_jira_issue(summary: str, issue_type: str, parent_key: str, description: str = "") -> Issue: | ||
| """ | ||
| Creates a Jira issue (Story or Sub-task) in the configured project. | ||
|
|
||
| Args: | ||
| summary (str): The summary of the new issue. | ||
| issue_type (str): The type of issue to create (e.g., "Story", "Sub-task"). | ||
| parent_key (str): The key of the Epic (for Story) or Story (for Sub-task). | ||
| description (str): The description of the issue. | ||
|
|
||
| Returns: | ||
| Issue: The newly created Jira issue. | ||
|
|
||
| Raises: | ||
| KeyError: If required environment variables are missing. | ||
| """ | ||
| project = os.environ["MIGRATION_BACKLOG_JIRA_PROJECT"] | ||
| epic_link_field = os.environ["MIGRATION_BACKLOG_JIRA_EPIC_LINK_FIELD"] | ||
| fields = { | ||
| "project": {"key": project}, | ||
| "summary": summary, | ||
| "issuetype": {"name": issue_type}, | ||
| "description": description, | ||
| } | ||
|
|
||
| if issue_type == "Story": | ||
| fields[epic_link_field] = parent_key | ||
| elif issue_type == "Sub-task": | ||
| fields["parent"] = {"key": parent_key} | ||
|
|
||
| return get_jira_client().create_issue(fields=fields) | ||
|
|
||
|
|
||
| def get_or_create_jira_issue(summary: str, jql: str, issue_type: str, parent_key, description: str = "") -> Issue: | ||
| """ | ||
| Retrieves a Jira issue using a JQL query with an exact summary match, or creates it if not found. | ||
|
|
||
| Args: | ||
| summary (str): The summary of the issue. | ||
| jql (str): The JQL query to search for the issue. | ||
| issue_type (str): The type of issue to create if not found. | ||
| parent_key (Optional[str]): The parent key (Epic or Story) if the issue needs to be linked. | ||
| description (str): The description of the issue. | ||
|
|
||
| Returns: | ||
| Issue: The existing or newly created Jira issue. | ||
|
|
||
| Raises: | ||
| KeyError: If required environment variables are missing. | ||
| """ | ||
| if issue := get_jira_issue_by_jql(jql=jql, summary=summary): | ||
| return issue | ||
|
|
||
| return create_jira_issue(summary=summary, issue_type=issue_type, parent_key=parent_key, description=description) | ||
|
|
||
|
|
||
| def get_or_create_story(summary: str, epic_key: str, description: str = "") -> Issue: | ||
| """ | ||
| Retrieves or creates a Jira Story linked to the specified Epic. | ||
|
|
||
| Args: | ||
| summary (str): The summary of the Story. | ||
| epic_key (str): The key of the Epic. | ||
| description (str): The description of the Story. | ||
|
|
||
| Returns: | ||
| Issue: The existing or newly created Story issue. | ||
| """ | ||
| project = os.environ["MIGRATION_BACKLOG_JIRA_PROJECT"] | ||
| jql = f'project = {project} AND summary ~ "{summary}" AND issuetype = Story AND "Epic Link" = "{epic_key}"' | ||
|
|
||
| return get_or_create_jira_issue( | ||
| summary=summary, | ||
| jql=jql, | ||
| issue_type="Story", | ||
| parent_key=epic_key, | ||
| description=description, | ||
| ) | ||
|
|
||
|
|
||
| def get_or_create_subtask(summary: str, story_key: str, description: str = "") -> Issue: | ||
| """ | ||
| Retrieves or creates a Jira Sub-task under the specified Story. | ||
|
|
||
| Args: | ||
| summary (str): The summary of the Sub-task. | ||
| story_key (str): The key of the parent Story. | ||
| description (str): The description of the Sub-task. | ||
|
|
||
| Returns: | ||
| Issue: The existing or newly created Sub-task issue. | ||
| """ | ||
| project = os.environ["MIGRATION_BACKLOG_JIRA_PROJECT"] | ||
| jql = f'project = {project} AND summary ~ "{summary}" AND issuetype = Sub-task AND parent = "{story_key}"' | ||
|
|
||
| return get_or_create_jira_issue( | ||
| summary=summary, | ||
| jql=jql, | ||
| issue_type="Sub-task", | ||
| parent_key=story_key, | ||
| description=description, | ||
| ) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| parser = argparse.ArgumentParser(description="Create Jira Story and Sub-task if not present.") | ||
| parser.add_argument("--story-summary", required=True, help="Summary for the story.") | ||
| parser.add_argument("--story-description", default="", help="Description for the story.") | ||
| parser.add_argument("--subtask-summary", required=True, help="Summary for the sub-task.") | ||
| parser.add_argument("--subtask-description", default="", help="Description for the sub-task.") | ||
| args = parser.parse_args() | ||
|
|
||
| epic_key = os.environ["MIGRATION_BACKLOG_JIRA_EPIC_KEY"] | ||
|
|
||
| story = get_or_create_story( | ||
| summary=args.story_summary, | ||
| epic_key=epic_key, | ||
| description=args.story_description, | ||
| ) | ||
| get_or_create_subtask( | ||
| summary=args.subtask_summary, | ||
| story_key=story.key, | ||
| description=args.subtask_description, | ||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add some logging indicating the success of the story or subtask created...
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| print(f"Jira story '{story.fields.summary}' and sub-task '{args.subtask_summary}' created or already exist.") | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of select the environment?