Skip to content

Commit

Permalink
Introduce a time-sensitive block
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphbean committed Mar 7, 2024
1 parent d3fc040 commit f16d9ee
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 3 deletions.
53 changes: 51 additions & 2 deletions src/rules/team/rank.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
improvements that have been ranked very high.
"""

import datetime

import jira


Expand Down Expand Up @@ -89,6 +91,9 @@ def __init__(self, parent):
self.parent_issue = parent
self.issues = []

def yield_issues(self):
yield from self.issues

def parent_is_inprogress(self):
if self.parent_issue is None:
return False
Expand All @@ -112,9 +117,39 @@ def claims(self, issue) -> bool:
return self.parent_issue == parent_issue


class TimeSensitiveBlock(Block):
"""A special-case block that gets ranked to the top"""

def yield_issues(self):
if not self.issues:
return
duedate_field_id = self.issues[0].raw["Context"]["Field Ids"]["Due Date"]
duedate = lambda issue: getattr(issue.fields, duedate_field_id)
yield from sorted(self.issues, key=duedate)

@property
def rank(self):
return float("inf")

def parent_is_inprogress(self):
return False

def claims(self, issue) -> bool:
return self._claims(issue)

@staticmethod
def _claims(issue) -> bool:
duedate_field_id = issue.raw["Context"]["Field Ids"]["Due Date"]
critical_deadline = (
datetime.datetime.today() + datetime.timedelta(days=30 * 6)
).strftime("%Y-%m-%d")
duedate = getattr(issue.fields, duedate_field_id)
return duedate and duedate < critical_deadline


class Blocks(list):
def __init__(self, issues: list[jira.resources.Issue]) -> None:
self.blocks = []
self.blocks = [TimeSensitiveBlock(None)]
for issue in issues:
self.add_issue(issue)

Expand Down Expand Up @@ -142,14 +177,15 @@ def get_issues(self) -> list[jira.resources.Issue]:
== block.issues[0].fields.project.key
):
issues.append(block.parent_issue)
for issue in block.issues:
for issue in block.yield_issues():
issues.append(issue)
return issues

def sort(self):
self._sort_by_project_rank()
self._sort_by_status()
self._deprioritize_orphan_blocks()
self._prioritize_timesensitive_block()

def _sort_by_project_rank(self):
"""Rerank blocks based on the block's project rank.
Expand Down Expand Up @@ -213,3 +249,16 @@ def _deprioritize_orphan_blocks(self):
children.append(block)

self.blocks = children + orphans

def _prioritize_timesensitive_block(self):
"""Issues that are time sensitive rise to the top of the list."""
timesensitive = []
other = []

for block in self.blocks:
if type(block) is TimeSensitiveBlock:
timesensitive.append(block)
else:
other.append(block)

self.blocks = timesensitive + other
31 changes: 30 additions & 1 deletion src/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import datetime

import mock
import pytest


class MockIssue:
def __init__(self, idx, project, parent, rank):
def __init__(self, idx, project, parent, rank, duedate=None):
raw = {}
raw["Context"] = {}
raw["Context"]["Field Ids"] = {}
raw["Context"]["Field Ids"]["Rank"] = "rank"
raw["Context"]["Field Ids"]["Due Date"] = "duedate"
raw["Context"]["Related Issues"] = {}
raw["Context"]["Related Issues"]["Parent"] = parent

Expand All @@ -17,6 +20,7 @@ def __init__(self, idx, project, parent, rank):
self.fields = mock.MagicMock()
self.fields.project.key = project
self.fields.rank = rank
self.fields.duedate = duedate

def __repr__(self):
return f"<{type(self).__name__} {self.fields.project.key}-{self.idx}({self.fields.rank})>"
Expand All @@ -36,3 +40,28 @@ def issues():
return dict(
child0=child0, child1=child1, child2=child2, child3=child3, child4=child4
)


@pytest.fixture
def issues_with_due_dates():
project = "TESTPROJECT"
parent1 = MockIssue("parent1", project, None, 1)
child1 = MockIssue("child1", project, parent1, 2)
parent2 = MockIssue("parent2", project, None, 3)
child2 = MockIssue("child2", project, parent2, 4)
fmt = "%Y-%m-%d"
duedate = (datetime.datetime.today() + datetime.timedelta(days=30 * 7)).strftime(
fmt
)
child3 = MockIssue("child3", project, parent2, 5, duedate=duedate)
duedate = (datetime.datetime.today() + datetime.timedelta(days=30 * 2)).strftime(
fmt
)
child4 = MockIssue("child4", project, parent2, 6, duedate=duedate)
duedate = (datetime.datetime.today() + datetime.timedelta(days=30 * 1)).strftime(
fmt
)
child5 = MockIssue("child5", project, parent2, 7, duedate=duedate)
return dict(
child1=child1, child2=child2, child3=child3, child4=child4, child5=child5
)
20 changes: 20 additions & 0 deletions src/tests/test_rank.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,23 @@ def test_rank_single_move(issues):
assert new_ranking[5].key == "child2"
assert new_ranking[6].key == "child0"
assert new_ranking[7].key == "child4"


def test_rank_with_dates(issues_with_due_dates):
issues = issues_with_due_dates
issues["child3"].raw["Context"]["Related Issues"]["Parent"].fields.rank = -1
blocks = rules.team.rank.Blocks(list(issues.values()))
old_ranking = blocks.get_issues()
blocks.sort()
new_ranking = blocks.get_issues()
import pprint

pprint.pprint(new_ranking)
assert new_ranking != old_ranking
assert new_ranking[0].key == "child5"
assert new_ranking[1].key == "child4"
assert new_ranking[2].key == "parent2"
assert new_ranking[3].key == "child2"
assert new_ranking[4].key == "child3"
assert new_ranking[5].key == "parent1"
assert new_ranking[6].key == "child1"

0 comments on commit f16d9ee

Please sign in to comment.