Skip to content

Commit

Permalink
Merge pull request #146 from JohnStrunk/rollup-status-counts
Browse files Browse the repository at this point in the history
rollup status counts
  • Loading branch information
mergify[bot] authored Jul 1, 2024
2 parents 4d45bd1 + 65047e9 commit f3d20eb
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 14 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ The following variables are required:

- `ALLOWED_PROJECTS`: A comma-separated list of Jira project keys that the bot
is allowed to summarize (e.g., `ALLOWED_PROJECTS=OCTO,OCTOET`) (for the bot)
- `CONFLUENCE_TOKEN`: A Confluence PAT token that will allow updating the
Confluence pages with the summaries (for `rollup_status.py`)
- `CONFLUENCE_URL`: The URL for the Confluence instance (e.g., `https://...`)
- `GENAI_API`: The API endpoint for the IBM AI model (e.g., `https://...`)
- `GENAI_KEY`: Your API key for the IBM AI model
- `JIRA_TOKEN`: A JIRA PAT token that will allow retrieving issues from Jira as
Expand Down
14 changes: 13 additions & 1 deletion cfhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ def add(self, content: int | str | ET.Element) -> "CFElement":
>>> e = CFElement("p").add("Hello, ").add(CFElement("b", content="world")).add("!")
>>> print(ET.tostring(e, encoding="unicode"))
<p>Hello, <b>world</b>!</p>
# Simple text can be concatenated as well
>>> e = CFElement("p").add("Hello, ").add("world").add("!")
>>> print(ET.tostring(e, encoding="unicode"))
<p>Hello, world!</p>
# Multiple elements
>>> e = CFElement("p").add(CFElement("b", content="Hello")).add(", ")
>>> _ = e.add(CFElement("i", content="world")).add("!")
>>> print(ET.tostring(e, encoding="unicode"))
<p><b>Hello</b>, <i>world</i>!</p>
"""
if isinstance(content, int):
content = str(content)
Expand All @@ -111,7 +122,8 @@ def add(self, content: int | str | ET.Element) -> "CFElement":
self[-1].tail = self[-1].tail or ""
self[-1].tail += content
else:
self.text = content
self.text = self.text or ""
self.text += content
else:
self.append(content)
return self
Expand Down
33 changes: 33 additions & 0 deletions jira_howto.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,39 @@
"\n",
"print(f\"\\n\\nI am: {jiraissues.get_self(jira)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import summarizer\n",
"summarizer = importlib.reload(summarizer)\n",
"\n",
"i = jiraissues.issue_cache.get_issue(jira, \"OCTOET-77\")\n",
"print(summarizer.is_active(i, 14))\n",
"print(summarizer.is_active(i, 14, True))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from jiraissues import descendants, issue_cache\n",
"import rollup_status\n",
"rollup_status = importlib.reload(rollup_status)\n",
"\n",
"i = jiraissues.issue_cache.get_issue(jira, \"OCTOET-85\")\n",
"dkeys = descendants(jira, i.key)\n",
"print(dkeys)\n",
"\n",
"cats = rollup_status.categorize_issues(\n",
" {issue_cache.get_issue(jira, k) for k in dkeys}, 14)\n",
"pprint(cats)"
]
}
],
"metadata": {
Expand Down
166 changes: 166 additions & 0 deletions rollup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: initiative-rollups
spec:
schedule: "0 5 * * 1"
jobTemplate:
spec:
template:
spec:
containers:
##################################################
## OCTO-1
- name: rollup-1
image: ghcr.io/johnstrunk/jira-summarizer:latest
command:
- "/app/.venv/bin/python"
- "rollup-status.py"
args:
- "--log-level"
- "INFO"
- "--parent"
- "Initiative summaries"
- OCTO-1
envFrom:
- secretRef:
name: jira-summarizer-secret
optional: false
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "1000m"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
##################################################
## OCTO-2
- name: rollup-2
image: ghcr.io/johnstrunk/jira-summarizer:latest
command:
- "/app/.venv/bin/python"
- "rollup-status.py"
args:
- "--log-level"
- "INFO"
- "--parent"
- "Initiative summaries"
- OCTO-2
envFrom:
- secretRef:
name: jira-summarizer-secret
optional: false
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "1000m"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
##################################################
## OCTO-3
- name: rollup-3
image: ghcr.io/johnstrunk/jira-summarizer:latest
command:
- "/app/.venv/bin/python"
- "rollup-status.py"
args:
- "--log-level"
- "INFO"
- "--parent"
- "Initiative summaries"
- OCTO-3
envFrom:
- secretRef:
name: jira-summarizer-secret
optional: false
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "1000m"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
##################################################
## OCTO-4
- name: rollup-4
image: ghcr.io/johnstrunk/jira-summarizer:latest
command:
- "/app/.venv/bin/python"
- "rollup-status.py"
args:
- "--log-level"
- "INFO"
- "--parent"
- "Initiative summaries"
- OCTO-4
envFrom:
- secretRef:
name: jira-summarizer-secret
optional: false
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "1000m"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
##################################################
## OCTO-6
- name: rollup-6
image: ghcr.io/johnstrunk/jira-summarizer:latest
command:
- "/app/.venv/bin/python"
- "rollup-status.py"
args:
- "--log-level"
- "INFO"
- "--parent"
- "Initiative summaries"
- OCTO-6
envFrom:
- secretRef:
name: jira-summarizer-secret
optional: false
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "1000m"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
securityContext:
runAsNonRoot: true
terminationGracePeriodSeconds: 10
restartPolicy: OnFailure
65 changes: 54 additions & 11 deletions rollup_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from atlassian import Confluence, Jira # type: ignore

from cfhelper import CFElement, jiralink
from jiraissues import Issue, User, issue_cache
from jiraissues import Issue, User, descendants, issue_cache
from simplestats import Timer
from summarizer import get_chat_model, is_active, rollup_contributors, summarize_issue

Expand Down Expand Up @@ -104,6 +104,33 @@ def element_contrib_list(header: str, contributors: set[User]) -> CFElement:
)


def categorize_issues(issues: set[Issue], inactive_days: int) -> dict[str, set[Issue]]:
"""
Categorize issues by status
Parameters:
- issues: The set of issues to categorize
- inactive_days: Number of days before an issue is considered inactive
Returns:
A dictionary of categorized issues
"""
categorized: dict[str, set[Issue]] = {}

categorized["active"] = {
issue for issue in issues if is_active(issue, inactive_days, False)
}
categorized["inactive"] = {
issue for issue in issues if not is_active(issue, inactive_days, False)
}
categorized["closed"] = {issue for issue in issues if issue.status == "Closed"}
categorized["backlog"] = {
issue for issue in issues if issue.status in ["Backlog", "New", "ToDo"]
}

return categorized


def main() -> None: # pylint: disable=too-many-locals,too-many-statements
"""Main function"""
# pylint: disable=duplicate-code
Expand Down Expand Up @@ -144,8 +171,8 @@ def main() -> None: # pylint: disable=too-many-locals,too-many-statements
stime.start()
logging.info("Collecting issue summaries for children of %s", issue_key)
child_inputs: list[IssueSummary] = []
epic = issue_cache.get_issue(jclient, issue_key)
for child in epic.children:
initiative = issue_cache.get_issue(jclient, issue_key)
for child in initiative.children:
issue = issue_cache.get_issue(jclient, child.key)
if not is_active(issue, inactive_days, True):
logging.info("Skipping inactive issue %s", issue.key)
Expand Down Expand Up @@ -196,20 +223,15 @@ def main() -> None: # pylint: disable=too-many-locals,too-many-statements
"""
exec_paragraph = textwrap.fill(llm.invoke(prompt, stop=["<|endoftext|>"]).strip())

# Generate the overall status update
parent_page_id = lookup_page(cclient, args.parent)

page_title = f"Initiative status: {epic.key} - {epic.summary}"

# Root element for the page; tag doesn't matter as it will be stripped off later
page = CFElement("root")

# Top of the page; overall executive summary and initiative contributors
page.add(CFElement("h1", content="Executive Summary"))
page.add(CFElement("p", content=jiralink(epic.key)))
page.add(CFElement("p", content=jiralink(initiative.key)))
page.add(CFElement("p", content=exec_paragraph))
contributors = rollup_contributors(epic)
active_contributors = rollup_contributors(epic, active_days=inactive_days)
contributors = rollup_contributors(initiative)
active_contributors = rollup_contributors(initiative, active_days=inactive_days)
if active_contributors:
page.add(element_contrib_list("Active contributors", active_contributors))
if contributors:
Expand All @@ -229,6 +251,27 @@ def main() -> None: # pylint: disable=too-many-locals,too-many-statements
if item.contributors:
page.add(element_contrib_list("All contributors", item.contributors))

# Create counts for all descendant issues of the current epic issue
desc_keys = descendants(jclient, issue.key)
cats = categorize_issues(
{issue_cache.get_issue(jclient, key) for key in desc_keys},
inactive_days,
)
d_tag = CFElement("p", content=CFElement("b", content="Sub-issues: "))
counts: list[str] = []
if cats["active"]:
counts.append(f"{len(cats['active'])}(Active)")
if cats["closed"]:
counts.append(f"{len(cats['closed'])}(Closed)")
if cats["backlog"]:
counts.append(f"{len(cats['backlog'])}(Backlog)")
d_tag.add(", ".join(counts))
d_tag.add(f" — Total {len(desc_keys)}")
page.add(d_tag)

# Post the page to Confluence
parent_page_id = lookup_page(cclient, args.parent)
page_title = f"Initiative status: {initiative.key} - {initiative.summary}"
cclient.update_or_create(parent_page_id, page_title, page.unwrap())


Expand Down
4 changes: 2 additions & 2 deletions summarize-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ spec:
matchLabels:
app: summarize-api
strategy:
type: Recreate
type: RollingUpdate
template:
metadata:
labels:
Expand Down Expand Up @@ -85,7 +85,7 @@ spec:
mountPath: /tmp
securityContext:
runAsNonRoot: true
terminationGracePeriodSeconds: 10
terminationGracePeriodSeconds: 30
volumes:
- name: tmp-volume
emptyDir: {}

0 comments on commit f3d20eb

Please sign in to comment.