diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py index 140283b661ad5daac775ec288437c0740d9f61f7..975be94c4a856f0b587b4963885c7ed1d5c75a79 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py @@ -49,6 +49,7 @@ class SubtaskState(AbstractChoice): CANCELLED = "cancelled" ERROR = "error" UNSCHEDULABLE = "unschedulable" + OBSOLETE = "obsolete" class SubtaskType(AbstractChoice): diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py index 51d86953e7d42006bb8b03f59ccb45c01607b040..a9971f0feb61b69687b8c2575f458ebe1bae8cf2 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py @@ -146,6 +146,7 @@ def populate_subtask_allowed_state_transitions(apps, schema_editor): CANCELLED = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLED.value) ERROR = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value) UNSCHEDULABLE = SubtaskState.objects.get(value=SubtaskState.Choices.UNSCHEDULABLE.value) + OBSOLETE = SubtaskState.objects.get(value=SubtaskState.Choices.OBSOLETE.value) SubtaskAllowedStateTransitions.objects.bulk_create([ SubtaskAllowedStateTransitions(old_state=None, new_state=DEFINING), @@ -174,6 +175,10 @@ def populate_subtask_allowed_state_transitions(apps, schema_editor): SubtaskAllowedStateTransitions(old_state=FINISHING, new_state=ERROR), SubtaskAllowedStateTransitions(old_state=CANCELLING, new_state=ERROR), + # allow transition from the "end"-states cancelled/error to obsolete to indicate user-intent + SubtaskAllowedStateTransitions(old_state=CANCELLED, new_state=OBSOLETE), + SubtaskAllowedStateTransitions(old_state=ERROR, new_state=OBSOLETE), + SubtaskAllowedStateTransitions(old_state=DEFINED, new_state=CANCELLING), SubtaskAllowedStateTransitions(old_state=SCHEDULED, new_state=CANCELLING), SubtaskAllowedStateTransitions(old_state=QUEUED, new_state=CANCELLING), diff --git a/SAS/TMSS/backend/test/t_subtasks.py b/SAS/TMSS/backend/test/t_subtasks.py index 3dc4fb35502eecab07637f655ee2f638ef66c3d4..2108fdaf80e2d881846bd8b7c09dd7b2185e828f 100755 --- a/SAS/TMSS/backend/test/t_subtasks.py +++ b/SAS/TMSS/backend/test/t_subtasks.py @@ -744,11 +744,11 @@ class SubtaskAllowedStateTransitionsTest(unittest.TestCase): # there should be no state to go to from FINISHED self.assertEqual(0, SubtaskAllowedStateTransitions.objects.filter(old_state__value=SubtaskState.Choices.FINISHED.value).count()) - # there should be no state to go to from CANCELLED - self.assertEqual(0, SubtaskAllowedStateTransitions.objects.filter(old_state__value=SubtaskState.Choices.CANCELLED.value).count()) + # there should be no state to go to from OBSOLETE + self.assertEqual(0, SubtaskAllowedStateTransitions.objects.filter(old_state__value=SubtaskState.Choices.OBSOLETE.value).count()) def test_illegal_state_transitions(self): - for state_value in [choice.value for choice in SubtaskState.Choices]: + for state_value in [SubtaskState.Choices.OBSOLETE.value]: #[choice.value for choice in SubtaskState.Choices]: # assume helper method set_subtask_state_following_allowed_transitions is working (see other tests above) # use it to create subtask in desired initial state subtask = models.Subtask.objects.create(**Subtask_test_data(state=SubtaskState.objects.get(value=SubtaskState.Choices.DEFINING.value))) diff --git a/SAS/TMSS/backend/test/test_utils.py b/SAS/TMSS/backend/test/test_utils.py index cd815fcc66b5f907b2344c3cba6d775e7f863e30..39457890fd86bd48263e10c96ad7653282e0ea03 100644 --- a/SAS/TMSS/backend/test/test_utils.py +++ b/SAS/TMSS/backend/test/test_utils.py @@ -92,7 +92,7 @@ def set_subtask_state_following_allowed_transitions(subtask: typing.Union[Subtas subtask = Subtask.objects.get(id=subtask) # end states that we cannot get out of accoring to the design - END_STATE_VALUES = (SubtaskState.Choices.FINISHED.value, SubtaskState.Choices.UNSCHEDULABLE.value, SubtaskState.Choices.CANCELLED.value) + END_STATE_VALUES = (SubtaskState.Choices.FINISHED.value, SubtaskState.Choices.UNSCHEDULABLE.value, SubtaskState.Choices.OBSOLETE.value) while subtask.state.value != desired_state_value and (subtask.state.value not in END_STATE_VALUES): # handle "unsuccessful path" to cancelled/canceling end state @@ -112,6 +112,17 @@ def set_subtask_state_following_allowed_transitions(subtask: typing.Union[Subtas SubtaskState.Choices.CANCELLING.value): subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value) + # handle "unsuccessful path" to OBSOLETE end state (via CANCELLED) + elif desired_state_value == SubtaskState.Choices.OBSOLETE.value: + if subtask.state.value == SubtaskState.Choices.DEFINING.value: + subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value) + elif subtask.state.value in (SubtaskState.Choices.DEFINED.value, SubtaskState.Choices.SCHEDULED.value, SubtaskState.Choices.QUEUED.value, SubtaskState.Choices.ERROR.value): + subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLING.value) + elif subtask.state.value == SubtaskState.Choices.CANCELLING.value: + subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLED.value) + elif subtask.state.value == SubtaskState.Choices.CANCELLED.value: + subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.OBSOLETE.value) + # handle "unsuccessful path" to unschedulable end state elif desired_state_value == SubtaskState.Choices.UNSCHEDULABLE.value and subtask.state.value == SubtaskState.Choices.SCHEDULING.value: subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.UNSCHEDULABLE.value)