Skip to content

Commit 50bdad3

Browse files
committed
task completeで保留中タスクをデフォルト除外する
1 parent cec0976 commit 50bdad3

2 files changed

Lines changed: 121 additions & 4 deletions

File tree

annofabcli/task/complete_tasks.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@
3535

3636

3737
class CompleteTasksMain(CommandLineWithConfirm):
38-
def __init__(self, service: annofabapi.Resource, all_yes: bool = False) -> None: # noqa: FBT001, FBT002
38+
def __init__(self, service: annofabapi.Resource, all_yes: bool = False, include_on_hold_task: bool = False) -> None: # noqa: FBT001, FBT002
3939
self.service = service
4040
self.facade = AnnofabApiFacade(service)
41+
self.include_on_hold_task = include_on_hold_task
4142
CommandLineWithConfirm.__init__(self, all_yes)
4243

4344
def reply_inspection_comment(
@@ -286,8 +287,7 @@ def complete_task_for_inspection_acceptance_phase(
286287
logger.info(f"task_id='{task.task_id}' :: 検査/受入フェーズを次のフェーズに進めました。")
287288
return True
288289

289-
@staticmethod
290-
def _validate_task(task: Task, target_phase: TaskPhase, target_phase_stage: int, task_query: TaskQuery | None) -> bool:
290+
def _validate_task(self, task: Task, target_phase: TaskPhase, target_phase_stage: int, task_query: TaskQuery | None) -> bool:
291291
if not (task.phase == target_phase and task.phase_stage == target_phase_stage):
292292
logger.warning(f"task_id='{task.task_id}'のタスクは操作対象のフェーズ、フェーズステージではないため、スキップします。")
293293
return False
@@ -296,6 +296,10 @@ def _validate_task(task: Task, target_phase: TaskPhase, target_phase_stage: int,
296296
logger.warning(f"task_id='{task.task_id}'のタスクは作業中または完了状態であるため、スキップします。")
297297
return False
298298

299+
if task.status == TaskStatus.ON_HOLD and not self.include_on_hold_task:
300+
logger.warning(f"task_id='{task.task_id}'のタスクは保留中状態であるため、スキップします。保留中状態のタスクも次のフェーズに進める場合は、'--include_on_hold_task'を指定してください。")
301+
return False
302+
299303
if not match_task_with_query(task, task_query):
300304
logger.debug(f"task_id='{task.task_id}' は `--task_query` の条件にマッチしないため、スキップします。 :: task_query={task_query}")
301305
return False
@@ -468,7 +472,7 @@ def main(self) -> None:
468472
dict_task_query = annofabcli.common.cli.get_json_from_args(args.task_query)
469473
task_query: TaskQuery | None = TaskQuery.from_dict(dict_task_query) if dict_task_query is not None else None
470474

471-
main_obj = CompleteTasksMain(self.service, all_yes=self.all_yes)
475+
main_obj = CompleteTasksMain(self.service, all_yes=self.all_yes, include_on_hold_task=args.include_on_hold_task)
472476
main_obj.complete_task_list(
473477
project_id,
474478
task_id_list=task_id_list,
@@ -527,6 +531,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
527531

528532
argument_parser.add_task_query()
529533

534+
parser.add_argument(
535+
"--include_on_hold_task",
536+
action="store_true",
537+
help="指定した場合、保留中状態のタスクも次のフェーズに進めます。指定しない場合、保留中状態のタスクはスキップします。",
538+
)
539+
530540
parser.add_argument(
531541
"--parallelism",
532542
type=int,
@@ -553,6 +563,7 @@ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse
553563
"検査/受入フェーズを完了する場合は、未処置の検査コメントを対応完了/対応不要状態に変更できます"
554564
"(未処置の検査コメントが残っている状態では、タスクを合格にできないため)。"
555565
"作業中また完了状態のタスクは、次のフェーズに進めません。"
566+
"保留中状態のタスクは、デフォルトでは次のフェーズに進めません。"
556567
)
557568
epilog = "チェッカーまたはオーナロールを持つユーザで実行してください。"
558569

tests/task/test_complete_tasks.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
from unittest.mock import Mock
5+
6+
import pytest
7+
from annofabapi.models import TaskPhase
8+
9+
from annofabcli.task import complete_tasks
10+
11+
12+
def create_task_dict(*, status: str = "not_started", phase: str = "annotation") -> dict[str, object]:
13+
return {
14+
"project_id": "project1",
15+
"task_id": "task1",
16+
"phase": phase,
17+
"phase_stage": 1,
18+
"status": status,
19+
"input_data_id_list": ["input_data1"],
20+
"account_id": "account1",
21+
"histories_by_phase": [],
22+
"work_time_span": 0,
23+
"number_of_rejections": 0,
24+
"started_datetime": None,
25+
"updated_datetime": "2024-01-01T00:00:00+00:00",
26+
"operation_updated_datetime": None,
27+
"sampling": None,
28+
"metadata": {},
29+
}
30+
31+
32+
def test_complete_task_skips_on_hold_task_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
33+
service = Mock()
34+
service.wrapper.get_task_or_none.return_value = create_task_dict(status="on_hold")
35+
main_obj = complete_tasks.CompleteTasksMain(service, all_yes=True)
36+
complete_task_mock = Mock(return_value=True)
37+
monkeypatch.setattr(main_obj, "complete_task_for_annotation_phase", complete_task_mock)
38+
39+
result = main_obj.complete_task(
40+
project_id="project1",
41+
task_id="task1",
42+
target_phase=TaskPhase.ANNOTATION,
43+
target_phase_stage=1,
44+
)
45+
46+
assert result is False
47+
complete_task_mock.assert_not_called()
48+
49+
50+
def test_complete_task_allows_on_hold_task_when_option_is_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
51+
service = Mock()
52+
service.wrapper.get_task_or_none.return_value = create_task_dict(status="on_hold")
53+
main_obj = complete_tasks.CompleteTasksMain(service, all_yes=True, include_on_hold_task=True)
54+
complete_task_mock = Mock(return_value=True)
55+
monkeypatch.setattr(main_obj, "complete_task_for_annotation_phase", complete_task_mock)
56+
57+
result = main_obj.complete_task(
58+
project_id="project1",
59+
task_id="task1",
60+
target_phase=TaskPhase.ANNOTATION,
61+
target_phase_stage=1,
62+
)
63+
64+
assert result is True
65+
complete_task_mock.assert_called_once()
66+
67+
68+
def test_main_passes_include_on_hold_task_to_main_object(monkeypatch: pytest.MonkeyPatch) -> None:
69+
service = Mock()
70+
facade = Mock()
71+
args = argparse.Namespace(
72+
project_id="project1",
73+
task_id=["task1"],
74+
phase="annotation",
75+
phase_stage=1,
76+
inspection_status=None,
77+
reply_comment=None,
78+
task_query=None,
79+
parallelism=None,
80+
include_on_hold_task=True,
81+
yes=True,
82+
)
83+
84+
complete_task_list_mock = Mock()
85+
complete_tasks_main_init_mock = Mock()
86+
87+
class CompleteTasksMainStub:
88+
def __init__(self, _service, *, all_yes, include_on_hold_task):
89+
complete_tasks_main_init_mock(all_yes=all_yes, include_on_hold_task=include_on_hold_task)
90+
91+
def complete_task_list(self, *args, **kwargs):
92+
complete_task_list_mock(*args, **kwargs)
93+
94+
monkeypatch.setattr(complete_tasks, "CompleteTasksMain", CompleteTasksMainStub)
95+
monkeypatch.setattr(complete_tasks.annofabcli.common.cli, "get_list_from_args", lambda value: value)
96+
monkeypatch.setattr(complete_tasks.annofabcli.common.cli, "get_json_from_args", lambda _value: None)
97+
98+
command = complete_tasks.CompleteTasks(service, facade, args)
99+
monkeypatch.setattr(command, "validate_project", Mock())
100+
101+
command.main()
102+
103+
complete_tasks_main_init_mock.assert_called_once_with(all_yes=True, include_on_hold_task=True)
104+
complete_task_list_mock.assert_called_once()
105+
assert complete_task_list_mock.call_args.args[0] == "project1"
106+
assert complete_task_list_mock.call_args.kwargs["target_phase"] == TaskPhase.ANNOTATION

0 commit comments

Comments
 (0)