Skip to content
Snippets Groups Projects
Commit 9d26a5d9 authored by Todd Dembrey's avatar Todd Dembrey
Browse files

add the concept of steps to workflows, allow multi phase step and update template

parent c7f52b16
No related branches found
No related tags found
No related merge requests found
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<span>{{ object.round }}</span> <span>{{ object.round }}</span>
<span>Lead: {{ object.round.specific.lead }}</span> <span>Lead: {{ object.round.specific.lead }}</span>
</h5> </h5>
{% include "funds/includes/status_bar.html" with workflow=object.workflow status=object.status %} {% include "funds/includes/status_bar.html" with workflow=object.workflow status=object.phase %}
</div> </div>
</div> </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{# '.status-bar__item--is-complete' needs to be added to each 'complete' step #} {# '.status-bar__item--is-complete' needs to be added to each 'complete' step #}
{# '.status-bar__item--is-current' needs to be added to the current step #} {# '.status-bar__item--is-current' needs to be added to the current step #}
{% for phase in workflow %} {% for phase in workflow %}
<div class="status-bar__item {% if phase == status %}status-bar__item--is-current{% endif %}"> <div class="status-bar__item status-bar__item--{% if phase == status %}is-current{% elif phase.step < status.step %}is-complete{% endif %}">
<span class="status-bar__tooltip" data-title="{{ phase.name }}" aria-label="{{ phase.name }}"></span> <span class="status-bar__tooltip" data-title="{{ phase.name }}" aria-label="{{ phase.name }}"></span>
<svg class="status-bar__icon"><use xlink:href="#tick-alt"></use></svg> <svg class="status-bar__icon"><use xlink:href="#tick-alt"></use></svg>
</div> </div>
......
...@@ -58,6 +58,28 @@ class TestStageCreation(SimpleTestCase): ...@@ -58,6 +58,28 @@ class TestStageCreation(SimpleTestCase):
self.assertEqual(stage.name, name) self.assertEqual(stage.name, name)
self.assertEqual(stage.form, form) self.assertEqual(stage.form, form)
def test_can_create_with_multi_phase_step(self):
first_phase, second_phase = Phase(name='first'), Phase(name='second')
change_first = ChangePhaseAction(first_phase, 'first')
change_second = ChangePhaseAction(second_phase, 'second')
class PhaseSwitch(Phase):
actions = [change_first, change_second]
class MultiPhaseStep(Stage):
name = 'stage'
phases = [
PhaseSwitch(),
[first_phase, second_phase],
]
stage = MultiPhaseStep(None)
self.assertEqual(stage.steps, 2)
current_phase = stage.phases[0]
self.assertEqual(current_phase.process(change_first.name), stage.phases[1])
self.assertEqual(current_phase.process(change_second.name), stage.phases[2])
def test_can_get_next_phase(self): def test_can_get_next_phase(self):
stage = StageFactory.build(num_phases=2) stage = StageFactory.build(num_phases=2)
self.assertEqual(stage.next(stage.phases[0]), stage.phases[1]) self.assertEqual(stage.next(stage.phases[0]), stage.phases[1])
......
...@@ -12,14 +12,14 @@ Workflow -> Stage -> Phase -> Action ...@@ -12,14 +12,14 @@ Workflow -> Stage -> Phase -> Action
""" """
def phase_name(stage: 'Stage', phase: Union['Phase', str], occurrence: int) -> str: def phase_name(stage: 'Stage', phase: Union['Phase', str], step: int) -> str:
# Build the identifiable name for a phase # Build the identifiable name for a phase
if not isinstance(phase, str): if not isinstance(phase, str):
phase_name = phase._internal phase_name = phase._internal
else: else:
phase_name = phase phase_name = phase
return '__'.join([stage.name, phase_name, str(occurrence)]) return '__'.join([stage.name, phase_name, str(step)])
class Workflow(Iterable): class Workflow(Iterable):
...@@ -113,16 +113,25 @@ class Stage(Iterable): ...@@ -113,16 +113,25 @@ class Stage(Iterable):
# TODO: consider removing form from stage as the stage is generic and # TODO: consider removing form from stage as the stage is generic and
# shouldn't care about forms. # shouldn't care about forms.
self.form = form self.form = form
self.steps = len(self.phases)
# Make the phases new instances to prevent errors with mutability # Make the phases new instances to prevent errors with mutability
existing_phases: set = set() self.phases = self.copy_phases(self.phases)
new_phases: list = list()
for phase in self.phases: def copy_phases(self, phases):
phase.stage = self new_phases = list()
while str(phase) in existing_phases: for step, phase in enumerate(self.phases):
phase.occurrence += 1 try:
existing_phases.add(str(phase)) new_phases.append(self.copy_phase(phase, step))
new_phases.append(copy.copy(phase)) except AttributeError:
self.phases = new_phases # We have a step with multiple equivalent phases
for sub_phase in phase:
new_phases.append(self.copy_phase(sub_phase, step))
return new_phases
def copy_phase(self, phase, step: int):
phase.stage = self
phase.step = step
return copy.copy(phase)
def __iter__(self) -> Iterator['Phase']: def __iter__(self) -> Iterator['Phase']:
yield from self.phases yield from self.phases
...@@ -173,12 +182,12 @@ class Phase: ...@@ -173,12 +182,12 @@ class Phase:
self._internal = slugify(self.name) self._internal = slugify(self.name)
self.stage: Union['Stage', None] = None self.stage: Union['Stage', None] = None
self._actions = {action.name: action for action in self.actions} self._actions = {action.name: action for action in self.actions}
self.occurrence: int = 0 self.step : int = 0
def __eq__(self, other: Union[object, str]) -> bool: def __eq__(self, other: Union[object, str]) -> bool:
if isinstance(other, str): if isinstance(other, str):
return str(self) == other return str(self) == other
to_match = ['name', 'occurrence'] to_match = ['name', 'step']
return all(getattr(self, attr) == getattr(other, attr) for attr in to_match) return all(getattr(self, attr) == getattr(other, attr) for attr in to_match)
@property @property
...@@ -186,7 +195,7 @@ class Phase: ...@@ -186,7 +195,7 @@ class Phase:
return list(self._actions.keys()) return list(self._actions.keys())
def __str__(self) -> str: def __str__(self) -> str:
return phase_name(self.stage, self, self.occurrence) return phase_name(self.stage, self, self.step)
def __getitem__(self, value: str) -> 'Action': def __getitem__(self, value: str) -> 'Action':
return self._actions[value] return self._actions[value]
...@@ -275,8 +284,7 @@ class RequestStage(Stage): ...@@ -275,8 +284,7 @@ class RequestStage(Stage):
DiscussionWithNextPhase(), DiscussionWithNextPhase(),
ReviewPhase(), ReviewPhase(),
DiscussionPhase(), DiscussionPhase(),
accepted, [accepted, rejected]
rejected,
] ]
...@@ -285,8 +293,7 @@ class ConceptStage(Stage): ...@@ -285,8 +293,7 @@ class ConceptStage(Stage):
phases = [ phases = [
DiscussionWithNextPhase(), DiscussionWithNextPhase(),
ReviewPhase(), ReviewPhase(),
DiscussionWithProgressionPhase(), [DiscussionWithProgressionPhase(), rejected]
rejected,
] ]
...@@ -298,8 +305,7 @@ class ProposalStage(Stage): ...@@ -298,8 +305,7 @@ class ProposalStage(Stage):
DiscussionWithNextPhase(), DiscussionWithNextPhase(),
ReviewPhase('AC Review', public_name='In AC review'), ReviewPhase('AC Review', public_name='In AC review'),
DiscussionPhase(public_name='In AC review'), DiscussionPhase(public_name='In AC review'),
accepted, [accepted, rejected,]
rejected,
] ]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment