annofabcli 1.102.1__py3-none-any.whl → 1.103.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annofabcli/annotation/annotation_query.py +9 -29
- annofabcli/annotation/change_annotation_attributes.py +6 -14
- annofabcli/annotation/change_annotation_properties.py +5 -12
- annofabcli/annotation/copy_annotation.py +4 -10
- annofabcli/annotation/delete_annotation.py +10 -26
- annofabcli/annotation/dump_annotation.py +1 -4
- annofabcli/annotation/import_annotation.py +16 -40
- annofabcli/annotation/list_annotation.py +1 -4
- annofabcli/annotation/merge_segmentation.py +5 -15
- annofabcli/annotation/remove_segmentation_overlap.py +8 -29
- annofabcli/annotation/restore_annotation.py +3 -9
- annofabcli/annotation_specs/add_attribute_restriction.py +2 -8
- annofabcli/annotation_specs/attribute_restriction.py +2 -10
- annofabcli/annotation_specs/export_annotation_specs.py +1 -3
- annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -10
- annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +4 -10
- annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +1 -3
- annofabcli/annotation_specs/list_annotation_specs_attribute.py +7 -18
- annofabcli/annotation_specs/list_annotation_specs_choice.py +3 -8
- annofabcli/annotation_specs/list_annotation_specs_history.py +0 -1
- annofabcli/annotation_specs/list_annotation_specs_label.py +3 -8
- annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +4 -9
- annofabcli/annotation_specs/list_attribute_restriction.py +3 -9
- annofabcli/annotation_specs/put_label_color.py +1 -6
- annofabcli/comment/delete_comment.py +3 -9
- annofabcli/comment/list_all_comment.py +2 -4
- annofabcli/comment/list_comment.py +1 -4
- annofabcli/comment/put_comment.py +4 -13
- annofabcli/comment/put_comment_simply.py +2 -6
- annofabcli/comment/put_inspection_comment.py +2 -6
- annofabcli/comment/put_inspection_comment_simply.py +3 -6
- annofabcli/comment/put_onhold_comment.py +2 -6
- annofabcli/comment/put_onhold_comment_simply.py +2 -4
- annofabcli/common/cli.py +5 -43
- annofabcli/common/download.py +8 -25
- annofabcli/common/image.py +5 -9
- annofabcli/common/utils.py +1 -3
- annofabcli/common/visualize.py +2 -4
- annofabcli/filesystem/draw_annotation.py +8 -20
- annofabcli/filesystem/filter_annotation.py +7 -24
- annofabcli/filesystem/mask_user_info.py +3 -6
- annofabcli/filesystem/merge_annotation.py +2 -6
- annofabcli/input_data/change_input_data_name.py +3 -7
- annofabcli/input_data/copy_input_data.py +6 -14
- annofabcli/input_data/delete_input_data.py +7 -24
- annofabcli/input_data/delete_metadata_key_of_input_data.py +5 -16
- annofabcli/input_data/list_all_input_data.py +5 -14
- annofabcli/input_data/list_all_input_data_merged_task.py +8 -23
- annofabcli/input_data/list_input_data.py +5 -16
- annofabcli/input_data/put_input_data.py +7 -19
- annofabcli/input_data/update_metadata_of_input_data.py +6 -14
- annofabcli/instruction/list_instruction_history.py +0 -1
- annofabcli/instruction/upload_instruction.py +1 -4
- annofabcli/job/list_job.py +1 -2
- annofabcli/job/list_last_job.py +1 -3
- annofabcli/organization/list_organization.py +0 -1
- annofabcli/organization_member/change_organization_member.py +1 -3
- annofabcli/organization_member/delete_organization_member.py +2 -6
- annofabcli/organization_member/invite_organization_member.py +1 -3
- annofabcli/organization_member/list_organization_member.py +0 -1
- annofabcli/project/change_organization_of_project.py +257 -0
- annofabcli/project/change_project_status.py +2 -2
- annofabcli/project/copy_project.py +2 -7
- annofabcli/project/diff_projects.py +4 -16
- annofabcli/project/list_project.py +0 -1
- annofabcli/project/put_project.py +2 -6
- annofabcli/project/subcommand_project.py +2 -0
- annofabcli/project_member/change_project_members.py +2 -2
- annofabcli/project_member/copy_project_members.py +2 -7
- annofabcli/project_member/drop_project_members.py +1 -3
- annofabcli/project_member/invite_project_members.py +1 -3
- annofabcli/project_member/list_users.py +0 -1
- annofabcli/project_member/put_project_members.py +4 -12
- annofabcli/stat_visualization/mask_visualization_dir.py +6 -16
- annofabcli/stat_visualization/merge_visualization_dir.py +6 -18
- annofabcli/stat_visualization/summarize_whole_performance_csv.py +3 -7
- annofabcli/stat_visualization/write_graph.py +5 -15
- annofabcli/stat_visualization/write_performance_rating_csv.py +4 -12
- annofabcli/statistics/list_annotation_area.py +3 -7
- annofabcli/statistics/list_annotation_attribute.py +6 -15
- annofabcli/statistics/list_annotation_attribute_filled_count.py +9 -23
- annofabcli/statistics/list_annotation_count.py +18 -44
- annofabcli/statistics/list_annotation_duration.py +14 -40
- annofabcli/statistics/list_video_duration.py +2 -3
- annofabcli/statistics/list_worktime.py +0 -1
- annofabcli/statistics/scatter.py +3 -9
- annofabcli/statistics/summarize_task_count.py +7 -12
- annofabcli/statistics/summarize_task_count_by_task_id_group.py +3 -11
- annofabcli/statistics/summarize_task_count_by_user.py +1 -5
- annofabcli/statistics/visualization/dataframe/annotation_count.py +1 -3
- annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +3 -9
- annofabcli/statistics/visualization/dataframe/productivity_per_date.py +11 -23
- annofabcli/statistics/visualization/dataframe/project_performance.py +1 -3
- annofabcli/statistics/visualization/dataframe/task.py +2 -5
- annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +6 -20
- annofabcli/statistics/visualization/dataframe/user_performance.py +29 -88
- annofabcli/statistics/visualization/dataframe/whole_performance.py +4 -10
- annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +17 -49
- annofabcli/statistics/visualization/dataframe/worktime_per_date.py +3 -9
- annofabcli/statistics/visualization/filtering_query.py +2 -6
- annofabcli/statistics/visualization/project_dir.py +9 -26
- annofabcli/statistics/visualization/visualization_source_files.py +3 -10
- annofabcli/statistics/visualize_annotation_count.py +7 -21
- annofabcli/statistics/visualize_annotation_duration.py +7 -17
- annofabcli/statistics/visualize_statistics.py +17 -52
- annofabcli/statistics/visualize_video_duration.py +8 -19
- annofabcli/supplementary/delete_supplementary_data.py +7 -23
- annofabcli/supplementary/list_supplementary_data.py +1 -1
- annofabcli/supplementary/put_supplementary_data.py +5 -15
- annofabcli/task/cancel_acceptance.py +3 -4
- annofabcli/task/change_operator.py +3 -11
- annofabcli/task/change_status_to_break.py +1 -1
- annofabcli/task/change_status_to_on_hold.py +5 -18
- annofabcli/task/complete_tasks.py +8 -25
- annofabcli/task/copy_tasks.py +2 -3
- annofabcli/task/delete_metadata_key_of_task.py +2 -6
- annofabcli/task/delete_tasks.py +7 -25
- annofabcli/task/list_all_tasks.py +2 -4
- annofabcli/task/list_tasks.py +2 -6
- annofabcli/task/list_tasks_added_task_history.py +7 -21
- annofabcli/task/put_tasks.py +2 -3
- annofabcli/task/put_tasks_by_count.py +3 -7
- annofabcli/task/reject_tasks.py +7 -19
- annofabcli/task/update_metadata_of_task.py +1 -1
- annofabcli/task_history/list_all_task_history.py +2 -5
- annofabcli/task_history/list_task_history.py +0 -1
- annofabcli/task_history_event/list_all_task_history_event.py +4 -11
- annofabcli/task_history_event/list_worktime.py +4 -14
- {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/METADATA +1 -1
- annofabcli-1.103.0.dist-info/RECORD +215 -0
- annofabcli-1.102.1.dist-info/RECORD +0 -214
- {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/WHEEL +0 -0
- {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import copy
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import annofabapi
|
|
9
|
+
import more_itertools
|
|
10
|
+
import requests
|
|
11
|
+
from annofabapi.models import JobStatus, OrganizationMemberRole, ProjectJobType
|
|
12
|
+
|
|
13
|
+
import annofabcli
|
|
14
|
+
from annofabcli.common.cli import (
|
|
15
|
+
CommandLine,
|
|
16
|
+
CommandLineWithConfirm,
|
|
17
|
+
build_annofabapi_resource_and_login,
|
|
18
|
+
)
|
|
19
|
+
from annofabcli.common.facade import AnnofabApiFacade
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ChangeProjectOrganizationMain(CommandLineWithConfirm):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
service: annofabapi.Resource,
|
|
28
|
+
*,
|
|
29
|
+
is_force: bool = False,
|
|
30
|
+
all_yes: bool = False,
|
|
31
|
+
) -> None:
|
|
32
|
+
self.service = service
|
|
33
|
+
self.is_force = is_force
|
|
34
|
+
self.facade = AnnofabApiFacade(service)
|
|
35
|
+
super().__init__(all_yes)
|
|
36
|
+
|
|
37
|
+
async def wait_until_jobs_finished_async(self, jobs: list[dict[str, Any]]) -> None:
|
|
38
|
+
tasks = [
|
|
39
|
+
self.wait_until_job_finished_async(
|
|
40
|
+
project_id=job["project_id"],
|
|
41
|
+
job_type=ProjectJobType(job["job_type"]),
|
|
42
|
+
job_id=job["job_id"],
|
|
43
|
+
job_access_interval=60,
|
|
44
|
+
max_job_access=360,
|
|
45
|
+
)
|
|
46
|
+
for job in jobs
|
|
47
|
+
]
|
|
48
|
+
results = await asyncio.gather(*tasks)
|
|
49
|
+
success_count = 0
|
|
50
|
+
for result in results:
|
|
51
|
+
if result is not None and result == JobStatus.SUCCEEDED:
|
|
52
|
+
success_count += 1
|
|
53
|
+
|
|
54
|
+
logger.info(f"{success_count} 件のプロジェクトの組織の変更が成功しました。")
|
|
55
|
+
|
|
56
|
+
async def wait_until_job_finished_async(
|
|
57
|
+
self,
|
|
58
|
+
project_id: str,
|
|
59
|
+
job_type: ProjectJobType,
|
|
60
|
+
job_id: str,
|
|
61
|
+
job_access_interval: int = 60,
|
|
62
|
+
max_job_access: int = 360,
|
|
63
|
+
) -> Optional["JobStatus"]:
|
|
64
|
+
"""
|
|
65
|
+
指定したジョブが終了するまで非同期で待つ。
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
project_id: プロジェクトID
|
|
69
|
+
job_type: ジョブ種別
|
|
70
|
+
job_id: ジョブID。Noneの場合は、現在進行中のジョブが終了するまで待つ。
|
|
71
|
+
job_access_interval: ジョブにアクセスする間隔[sec]
|
|
72
|
+
max_job_access: ジョブに最大何回アクセスするか
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
指定した時間(アクセス頻度と回数)待った後のジョブのステータスを返す。
|
|
76
|
+
指定したジョブ(job_idがNoneの場合は現在進行中のジョブ)が存在しない場合は、Noneを返す。
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def get_job_from_job_id(arg_job_id: str) -> Optional[dict[str, Any]]:
|
|
80
|
+
content, _ = self.service.api.get_project_job(project_id, query_params={"type": job_type.value})
|
|
81
|
+
job_list = content["list"]
|
|
82
|
+
return more_itertools.first_true(job_list, pred=lambda e: e["job_id"] == arg_job_id)
|
|
83
|
+
|
|
84
|
+
job_access_count = 0
|
|
85
|
+
while True:
|
|
86
|
+
# API呼び出しは同期なので、スレッドでラップ
|
|
87
|
+
job = get_job_from_job_id(job_id)
|
|
88
|
+
if job is None:
|
|
89
|
+
logger.info(
|
|
90
|
+
"project_id='%s', job_id='%s', job_type='%s' のジョブは存在しません。",
|
|
91
|
+
project_id,
|
|
92
|
+
job_type.value,
|
|
93
|
+
job_id,
|
|
94
|
+
)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
job_access_count += 1
|
|
98
|
+
|
|
99
|
+
if job["job_status"] == JobStatus.SUCCEEDED.value:
|
|
100
|
+
logger.info(
|
|
101
|
+
"project_id='%s', job_id='%s', job_type='%s' のジョブが成功しました。",
|
|
102
|
+
project_id,
|
|
103
|
+
job_id,
|
|
104
|
+
job_type.value,
|
|
105
|
+
)
|
|
106
|
+
return JobStatus.SUCCEEDED
|
|
107
|
+
|
|
108
|
+
elif job["job_status"] == JobStatus.FAILED.value:
|
|
109
|
+
logger.info(
|
|
110
|
+
"project_id='%s', job_id='%s', job_type='%s' のジョブが失敗しました。:: errors='%s'",
|
|
111
|
+
project_id,
|
|
112
|
+
job_id,
|
|
113
|
+
job_type.value,
|
|
114
|
+
job["errors"],
|
|
115
|
+
)
|
|
116
|
+
return JobStatus.FAILED
|
|
117
|
+
|
|
118
|
+
elif job_access_count < max_job_access:
|
|
119
|
+
logger.info(
|
|
120
|
+
"project_id='%s', job_id='%s', job_type='%s' のジョブは進行中です。%d 秒間待ちます。",
|
|
121
|
+
project_id,
|
|
122
|
+
job_id,
|
|
123
|
+
job_type.value,
|
|
124
|
+
job_access_interval,
|
|
125
|
+
)
|
|
126
|
+
await asyncio.sleep(job_access_interval)
|
|
127
|
+
else:
|
|
128
|
+
logger.info(
|
|
129
|
+
"project_id='%s', job_id='%s', job_type='%s' のジョブは %.1f 分以上経過しても、終了しませんでした。",
|
|
130
|
+
project_id,
|
|
131
|
+
job["job_id"],
|
|
132
|
+
job_type.value,
|
|
133
|
+
job_access_interval * job_access_count / 60,
|
|
134
|
+
)
|
|
135
|
+
return JobStatus.PROGRESS
|
|
136
|
+
|
|
137
|
+
def change_organization_for_project(self, project_id: str, organization_name: str) -> Optional[dict[str, Any]]:
|
|
138
|
+
project = self.service.wrapper.get_project_or_none(project_id)
|
|
139
|
+
if project is None:
|
|
140
|
+
logger.warning(f"project_id='{project_id}'のプロジェクトは存在しないので、スキップします。")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
project_name = project["title"]
|
|
144
|
+
if project["project_status"] == "active":
|
|
145
|
+
if self.is_force:
|
|
146
|
+
if not self.confirm_processing(
|
|
147
|
+
f"project_id='{project_id}'のプロジェクトの状態を停止中にしたあと、所属する組織を'{organization_name}'に変更しますか? :: project_name='{project_name}'"
|
|
148
|
+
):
|
|
149
|
+
return None
|
|
150
|
+
request_body = copy.deepcopy(project)
|
|
151
|
+
request_body.update(
|
|
152
|
+
{
|
|
153
|
+
"status": "suspended",
|
|
154
|
+
"last_updated_datetime": project["updated_datetime"],
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
project, _ = self.service.api.put_project(project_id, request_body=request_body, query_params={"v": "2"})
|
|
158
|
+
logger.info(f"project_id='{project_id}'のプロジェクトのステータスを「停止中」に変更しました。 :: project_name='{project_name}'")
|
|
159
|
+
else:
|
|
160
|
+
logger.warning(
|
|
161
|
+
f"project_id='{project_id}'のプロジェクトのステータスは「進行中」のため、組織を変更できません。 `--force`オプションを指定すれば、停止中状態に変更した後組織を変更できます。"
|
|
162
|
+
)
|
|
163
|
+
return None
|
|
164
|
+
elif not self.confirm_processing(f"project_id='{project_id}'のプロジェクトの組織を'{organization_name}'に変更しますか? :: project_name='{project_name}'"):
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
assert project is not None
|
|
168
|
+
request_body = copy.deepcopy(project)
|
|
169
|
+
request_body["organization_name"] = organization_name
|
|
170
|
+
request_body["last_updated_datetime"] = project["updated_datetime"]
|
|
171
|
+
request_body["status"] = project["project_status"]
|
|
172
|
+
|
|
173
|
+
content, _ = self.service.api.put_project(project_id, request_body=request_body, query_params={"v": "2"})
|
|
174
|
+
job = content["job"]
|
|
175
|
+
logger.info(f"project_id='{project_id}'のプロジェクトの所属先組織を'{organization_name}'に変更するジョブを発行しました。 :: project_name='{project_name}', job_id='{job['job_id']}'")
|
|
176
|
+
return job
|
|
177
|
+
|
|
178
|
+
def change_organization_for_project_list(self, project_id_list: list[str], organization_name: str) -> list[dict[str, Any]]:
|
|
179
|
+
if not self.facade.contains_any_organization_member_role(organization_name, [OrganizationMemberRole.OWNER, OrganizationMemberRole.ADMINISTRATOR]):
|
|
180
|
+
logger.warning(f"変更先組織'{organization_name}'に対して管理者ロールまたはオーナロールでないため、プロジェクトの所属する組織を変更できません。")
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
logger.info(f"{len(project_id_list)} 件のプロジェクトの組織を'{organization_name}'に変更するジョブを発行します。")
|
|
184
|
+
|
|
185
|
+
job_list = []
|
|
186
|
+
for project_id in project_id_list:
|
|
187
|
+
try:
|
|
188
|
+
result = self.change_organization_for_project(project_id, organization_name)
|
|
189
|
+
if result is not None:
|
|
190
|
+
job_list.append(result)
|
|
191
|
+
except requests.HTTPError:
|
|
192
|
+
logger.warning(f"project_id='{project_id}'の組織変更でHTTPエラーが発生しました。", exc_info=True)
|
|
193
|
+
logger.info(f"{len(job_list)}/{len(project_id_list)}件のプロジェクトの組織を'{organization_name}'に変更するジョブを発行しました。")
|
|
194
|
+
return job_list
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ChangeProjectOrganization(CommandLine):
|
|
198
|
+
def main(self) -> None:
|
|
199
|
+
args = self.args
|
|
200
|
+
project_id_list = annofabcli.common.cli.get_list_from_args(args.project_id)
|
|
201
|
+
main_obj = ChangeProjectOrganizationMain(self.service, all_yes=args.yes, is_force=args.force)
|
|
202
|
+
|
|
203
|
+
job_list = main_obj.change_organization_for_project_list(project_id_list=project_id_list, organization_name=args.organization)
|
|
204
|
+
if len(job_list) == 0:
|
|
205
|
+
logger.info("組織を変更するジョブは発行されませんでした。終了します。")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# APIリクエストを減らすため、とりあえず60秒待ちます
|
|
209
|
+
seconds = 60
|
|
210
|
+
logger.info(f"ジョブの完了を{seconds}秒待ちます。")
|
|
211
|
+
time.sleep(seconds)
|
|
212
|
+
|
|
213
|
+
# すべてのジョブが完了するまで待つ
|
|
214
|
+
asyncio.run(main_obj.wait_until_jobs_finished_async(job_list))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main(args: argparse.Namespace) -> None:
|
|
218
|
+
service = build_annofabapi_resource_and_login(args)
|
|
219
|
+
facade = AnnofabApiFacade(service)
|
|
220
|
+
ChangeProjectOrganization(service, facade, args).main()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
224
|
+
parser.add_argument(
|
|
225
|
+
"-p",
|
|
226
|
+
"--project_id",
|
|
227
|
+
type=str,
|
|
228
|
+
required=True,
|
|
229
|
+
nargs="+",
|
|
230
|
+
help="対象プロジェクトのproject_idを指定します。 ``file://`` を先頭に付けると、project_idの一覧が記載されたファイルを指定できます。",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
parser.add_argument(
|
|
234
|
+
"-org",
|
|
235
|
+
"--organization",
|
|
236
|
+
type=str,
|
|
237
|
+
required=True,
|
|
238
|
+
help="変更後の組織名を指定してください。",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
parser.add_argument(
|
|
242
|
+
"--force",
|
|
243
|
+
action="store_true",
|
|
244
|
+
help="強制的に組織を変更します(将来拡張用)。",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
parser.set_defaults(subcommand_func=main)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
251
|
+
subcommand_name = "change_organization"
|
|
252
|
+
subcommand_help = "プロジェクトの所属する組織を変更します。"
|
|
253
|
+
epilog = "プロジェクトのオーナロール、変更先の組織の管理者またはオーナーロールを持つユーザで実行してください。"
|
|
254
|
+
|
|
255
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
|
|
256
|
+
parse_args(parser)
|
|
257
|
+
return parser
|
|
@@ -112,7 +112,7 @@ class ChanegProjectStatusMain:
|
|
|
112
112
|
return False
|
|
113
113
|
|
|
114
114
|
if not self.facade.contains_any_project_member_role(project_id, [ProjectMemberRole.OWNER]):
|
|
115
|
-
logger.warning(f"project_id={project_id}:
|
|
115
|
+
logger.warning(f"project_id={project_id}: オーナロールでないため、プロジェクトのステータスを変更できません。project_title={project['title']}")
|
|
116
116
|
return False
|
|
117
117
|
|
|
118
118
|
logger.debug(f"{project['title']} のステータスを{status.value} に変更します。project_id={project_id}")
|
|
@@ -187,7 +187,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
187
187
|
parser.add_argument(
|
|
188
188
|
"--force",
|
|
189
189
|
action="store_true",
|
|
190
|
-
help=f"`--status {ProjectStatus.SUSPENDED.value}`を指定している状態で、 ``--force`` を指定した場合、作業中タスクが残っていても停止状態に変更します。",
|
|
190
|
+
help=f"`--status {ProjectStatus.SUSPENDED.value}`を指定している状態で、 ``--force`` を指定した場合、作業中タスクが残っていても停止状態に変更します。",
|
|
191
191
|
)
|
|
192
192
|
|
|
193
193
|
parser.set_defaults(subcommand_func=main)
|
|
@@ -84,10 +84,7 @@ class CopyProject(CommandLine):
|
|
|
84
84
|
if copied_targets is not None:
|
|
85
85
|
logger.info(f"コピー対象: {[e.value for e in copied_targets]}")
|
|
86
86
|
|
|
87
|
-
confirm_message =
|
|
88
|
-
f"プロジェクト'{src_project_title}'(project_id='{src_project_id}')を、"
|
|
89
|
-
f"プロジェクト'{dest_title}'(project_id='{dest_project_id}') にコピーしますか?"
|
|
90
|
-
)
|
|
87
|
+
confirm_message = f"プロジェクト'{src_project_title}'(project_id='{src_project_id}')を、プロジェクト'{dest_title}'(project_id='{dest_project_id}') にコピーしますか?"
|
|
91
88
|
if not self.confirm_processing(confirm_message):
|
|
92
89
|
logger.info(f"プロジェクト'{src_project_title}'(project_id='{src_project_id}')をコピーせずに終了します。")
|
|
93
90
|
return
|
|
@@ -142,9 +139,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
142
139
|
|
|
143
140
|
argument_parser.add_project_id(help_message="コピー元のプロジェクトのproject_idを指定してください。")
|
|
144
141
|
|
|
145
|
-
parser.add_argument(
|
|
146
|
-
"--dest_project_id", type=str, help="新しいプロジェクトのproject_idを指定してください。省略した場合は UUIDv4 フォーマットになります。"
|
|
147
|
-
)
|
|
142
|
+
parser.add_argument("--dest_project_id", type=str, help="新しいプロジェクトのproject_idを指定してください。省略した場合は UUIDv4 フォーマットになります。")
|
|
148
143
|
parser.add_argument("--dest_title", type=str, required=True, help="新しいプロジェクトのタイトルを指定してください。")
|
|
149
144
|
parser.add_argument("--dest_overview", type=str, help="新しいプロジェクトの概要を指定してください。")
|
|
150
145
|
|
|
@@ -105,11 +105,7 @@ class DiffProjects(CommandLine):
|
|
|
105
105
|
user_ids2 = [e["user_id"] for e in sorted_members2]
|
|
106
106
|
|
|
107
107
|
if user_ids1 != user_ids2:
|
|
108
|
-
diff_message += (
|
|
109
|
-
f"### user_idのListに差分あり\n"
|
|
110
|
-
f"set(user_ids1) - set(user_ids2) = {set(user_ids1) - set(user_ids2)}\n"
|
|
111
|
-
f"set(user_ids2) - set(user_ids1) = {set(user_ids2) - set(user_ids1)}\n"
|
|
112
|
-
)
|
|
108
|
+
diff_message += f"### user_idのListに差分あり\nset(user_ids1) - set(user_ids2) = {set(user_ids1) - set(user_ids2)}\nset(user_ids2) - set(user_ids1) = {set(user_ids2) - set(user_ids1)}\n"
|
|
113
109
|
|
|
114
110
|
return True, diff_message
|
|
115
111
|
|
|
@@ -286,12 +282,8 @@ class DiffProjects(CommandLine):
|
|
|
286
282
|
diff_message += message
|
|
287
283
|
|
|
288
284
|
if DiffTarget.ANNOTATION_LABELS in diff_targets:
|
|
289
|
-
labels1_v1 = convert_annotation_specs_labels_v2_to_v1(
|
|
290
|
-
|
|
291
|
-
)
|
|
292
|
-
labels2_v1 = convert_annotation_specs_labels_v2_to_v1(
|
|
293
|
-
labels_v2=annotation_specs2["labels"], additionals_v2=annotation_specs2["additionals"]
|
|
294
|
-
)
|
|
285
|
+
labels1_v1 = convert_annotation_specs_labels_v2_to_v1(labels_v2=annotation_specs1["labels"], additionals_v2=annotation_specs1["additionals"])
|
|
286
|
+
labels2_v1 = convert_annotation_specs_labels_v2_to_v1(labels_v2=annotation_specs2["labels"], additionals_v2=annotation_specs2["additionals"])
|
|
295
287
|
|
|
296
288
|
bool_result, message = self.diff_labels_of_annotation_specs(labels1_v1, labels2_v1)
|
|
297
289
|
is_different = is_different or bool_result
|
|
@@ -393,11 +385,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
393
385
|
nargs="+",
|
|
394
386
|
choices=[e.value for e in choices],
|
|
395
387
|
default=["annotation_labels", "inspection_phrases", "members", "settings"],
|
|
396
|
-
help="比較する項目。指定しなければ全項目を比較する。"
|
|
397
|
-
"annotation_labels: アノテーション仕様のラベル情報, "
|
|
398
|
-
"inspection_phrases: 定型指摘,"
|
|
399
|
-
"members: プロジェクトメンバ,"
|
|
400
|
-
"settings: プロジェクト設定,",
|
|
388
|
+
help="比較する項目。指定しなければ全項目を比較する。annotation_labels: アノテーション仕様のラベル情報, inspection_phrases: 定型指摘,members: プロジェクトメンバ,settings: プロジェクト設定,",
|
|
401
389
|
)
|
|
402
390
|
|
|
403
391
|
parser.set_defaults(subcommand_func=main)
|
|
@@ -65,8 +65,7 @@ class PutProject(CommandLine):
|
|
|
65
65
|
}
|
|
66
66
|
new_project, _ = self.service.api.put_project(new_project_id, request_body=request_body)
|
|
67
67
|
logger.info(
|
|
68
|
-
f"'{organization}'組織に、project_id='{new_project['project_id']}'のプロジェクトを作成しました。 :: "
|
|
69
|
-
f"title='{new_project['title']}', input_data_type='{new_project['input_data_type']}'"
|
|
68
|
+
f"'{organization}'組織に、project_id='{new_project['project_id']}'のプロジェクトを作成しました。 :: title='{new_project['title']}', input_data_type='{new_project['input_data_type']}'"
|
|
70
69
|
)
|
|
71
70
|
|
|
72
71
|
COMMON_MESSAGE = "annofabcli project put: error:"
|
|
@@ -114,10 +113,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
114
113
|
type=str,
|
|
115
114
|
choices=[e.value for e in InputDataType],
|
|
116
115
|
required=True,
|
|
117
|
-
help="プロジェクトに登録する入力データの種類\n\n"
|
|
118
|
-
f" * {InputDataType.IMAGE.value} : 画像\n"
|
|
119
|
-
f" * {InputDataType.MOVIE.value} : 動画\n"
|
|
120
|
-
f" * {InputDataType.CUSTOM.value} : カスタム(点群など)",
|
|
116
|
+
help=f"プロジェクトに登録する入力データの種類\n\n * {InputDataType.IMAGE.value} : 画像\n * {InputDataType.MOVIE.value} : 動画\n * {InputDataType.CUSTOM.value} : カスタム(点群など)",
|
|
121
117
|
)
|
|
122
118
|
|
|
123
119
|
parser.add_argument("-p", "--project_id", type=str, required=False, help="作成するプロジェクトのproject_id。未指定の場合はUUIDv4になります。")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
+
import annofabcli.project.change_organization_of_project
|
|
4
5
|
import annofabcli.project.change_project_status
|
|
5
6
|
import annofabcli.project.copy_project
|
|
6
7
|
import annofabcli.project.diff_projects
|
|
@@ -13,6 +14,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
13
14
|
subparsers = parser.add_subparsers(dest="subcommand_name")
|
|
14
15
|
|
|
15
16
|
# サブコマンドの定義
|
|
17
|
+
annofabcli.project.change_organization_of_project.add_parser(subparsers)
|
|
16
18
|
annofabcli.project.change_project_status.add_parser(subparsers)
|
|
17
19
|
annofabcli.project.copy_project.add_parser(subparsers)
|
|
18
20
|
annofabcli.project.diff_projects.add_parser(subparsers)
|
|
@@ -147,7 +147,7 @@ class ChangeProjectMembers(CommandLine):
|
|
|
147
147
|
def main(self) -> None:
|
|
148
148
|
args = self.args
|
|
149
149
|
project_id = args.project_id
|
|
150
|
-
if args.all_user:
|
|
150
|
+
if args.all_user: # noqa: SIM108
|
|
151
151
|
user_id_list = self.get_all_user_id_list_except_myself(project_id)
|
|
152
152
|
else:
|
|
153
153
|
user_id_list = annofabcli.common.cli.get_list_from_args(args.user_id)
|
|
@@ -193,7 +193,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
193
193
|
parser.add_argument(
|
|
194
194
|
"--member_info",
|
|
195
195
|
type=str,
|
|
196
|
-
help="プロジェクトメンバに対して設定するメンバ情報を、JSON形式で指定します。 ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。 "
|
|
196
|
+
help="プロジェクトメンバに対して設定するメンバ情報を、JSON形式で指定します。 ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。 "
|
|
197
197
|
"以下のキーが指定可能です。sampling_inspection_rate, sampling_acceptance_rate, "
|
|
198
198
|
"未設定にする場合は、値にnullを指定してください。"
|
|
199
199
|
"詳細は https://annofab.com/docs/api/#operation/putProjectMember を参照ください。 ",
|
|
@@ -120,10 +120,7 @@ class CopyProjectMembers(CommandLine):
|
|
|
120
120
|
account_id = member["account_id"]
|
|
121
121
|
if self.find_member(dest_organization_members, account_id) is None:
|
|
122
122
|
# コピー先の組織メンバでないので、コピーしない
|
|
123
|
-
logger.debug(
|
|
124
|
-
f"コピーしないメンバ: {member['user_id']} , {member['username']} : "
|
|
125
|
-
f"(コピー先の所属組織 {dest_organization_name} の組織メンバでないため)"
|
|
126
|
-
)
|
|
123
|
+
logger.debug(f"コピーしないメンバ: {member['user_id']} , {member['username']} : (コピー先の所属組織 {dest_organization_name} の組織メンバでないため)")
|
|
127
124
|
continue
|
|
128
125
|
|
|
129
126
|
added_members.append(member)
|
|
@@ -152,9 +149,7 @@ class CopyProjectMembers(CommandLine):
|
|
|
152
149
|
|
|
153
150
|
if len(added_members) > 0:
|
|
154
151
|
if self.confirm_processing(
|
|
155
|
-
f"'{self.src_project_title}' のプロジェクトのメンバを、"
|
|
156
|
-
f"'{self.dest_project_title}' にコピーしますか?"
|
|
157
|
-
f"追加対象: {len(added_members)} 件, 削除対象: {len(deleted_dest_members)} 件"
|
|
152
|
+
f"'{self.src_project_title}' のプロジェクトのメンバを、'{self.dest_project_title}' にコピーしますか?追加対象: {len(added_members)} 件, 削除対象: {len(deleted_dest_members)} 件"
|
|
158
153
|
):
|
|
159
154
|
self.put_project_members(dest_project_id, updated_members)
|
|
160
155
|
else:
|
|
@@ -51,9 +51,7 @@ class DropProjectMembersMain:
|
|
|
51
51
|
self.service.api.put_project_member(project_id, user_id, request_body=request_body)
|
|
52
52
|
logger.debug(f"プロジェクト'{project_title}'(project_id='{project_id}') から、user_id='{user_id}'のユーザーを脱退させました。")
|
|
53
53
|
except requests.HTTPError:
|
|
54
|
-
logger.warning(
|
|
55
|
-
f"プロジェクト'{project_title}'(project_id='{project_id}') から、user_id='{user_id}'のユーザーを脱退させられませんでした。"
|
|
56
|
-
)
|
|
54
|
+
logger.warning(f"プロジェクト'{project_title}'(project_id='{project_id}') から、user_id='{user_id}'のユーザーを脱退させられませんでした。")
|
|
57
55
|
|
|
58
56
|
def drop_role_with_organization(self, organization_name: str, user_id_list: list[str]): # noqa: ANN201
|
|
59
57
|
projects = self.service.wrapper.get_all_projects_of_organization(organization_name, query_params={"account_id": self.service.api.account_id})
|
|
@@ -131,9 +131,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
131
131
|
nargs="+",
|
|
132
132
|
help="招待するプロジェクトのproject_idを指定してください。 ``file://`` を先頭に付けると、一覧が記載されたファイルを指定できます。",
|
|
133
133
|
)
|
|
134
|
-
assign_group.add_argument(
|
|
135
|
-
"-org", "--organization", type=str, help="組織名を指定すると、組織配下のすべてのプロジェクト(自分が所属している)に招待します。"
|
|
136
|
-
)
|
|
134
|
+
assign_group.add_argument("-org", "--organization", type=str, help="組織名を指定すると、組織配下のすべてのプロジェクト(自分が所属している)に招待します。")
|
|
137
135
|
|
|
138
136
|
parser.set_defaults(subcommand_func=main)
|
|
139
137
|
|
|
@@ -97,9 +97,7 @@ class PutProjectMembers(CommandLine):
|
|
|
97
97
|
logger.warning(f"ユーザ '{member.user_id}' は、'{organization_name}' 組織の組織メンバでないため、登録できませんでした。")
|
|
98
98
|
continue
|
|
99
99
|
|
|
100
|
-
message_for_confirm =
|
|
101
|
-
f"ユーザ '{member.user_id}'を、プロジェクト'{project_title}'のメンバーに登録しますか? member_role='{member.member_role.value}'"
|
|
102
|
-
)
|
|
100
|
+
message_for_confirm = f"ユーザ '{member.user_id}'を、プロジェクト'{project_title}'のメンバーに登録しますか? member_role='{member.member_role.value}'"
|
|
103
101
|
if not self.confirm_processing(message_for_confirm):
|
|
104
102
|
continue
|
|
105
103
|
|
|
@@ -110,9 +108,7 @@ class PutProjectMembers(CommandLine):
|
|
|
110
108
|
count_invite_members += 1
|
|
111
109
|
|
|
112
110
|
except requests.exceptions.HTTPError:
|
|
113
|
-
logger.warning(
|
|
114
|
-
f"プロジェクトメンバの登録に失敗しました。user_id = '{member.user_id}', member_role = '{member.member_role.value}'", exc_info=True
|
|
115
|
-
)
|
|
111
|
+
logger.warning(f"プロジェクトメンバの登録に失敗しました。user_id = '{member.user_id}', member_role = '{member.member_role.value}'", exc_info=True)
|
|
116
112
|
|
|
117
113
|
logger.info(f"プロジェクト'{project_title}' に、{count_invite_members} / {len(members)} 件のプロジェクトメンバを登録しました。")
|
|
118
114
|
|
|
@@ -120,9 +116,7 @@ class PutProjectMembers(CommandLine):
|
|
|
120
116
|
if delete:
|
|
121
117
|
user_id_list = [e.user_id for e in members]
|
|
122
118
|
# 自分自身は削除しないようにする
|
|
123
|
-
deleted_members = [
|
|
124
|
-
e for e in old_project_members if (e["user_id"] not in user_id_list and e["user_id"] != self.service.api.login_user_id)
|
|
125
|
-
]
|
|
119
|
+
deleted_members = [e for e in old_project_members if (e["user_id"] not in user_id_list and e["user_id"] != self.service.api.login_user_id)]
|
|
126
120
|
|
|
127
121
|
count_delete_members = 0
|
|
128
122
|
logger.info(f"プロジェクト '{project_title}' から、{len(deleted_members)} 件のプロジェクトメンバを削除します。")
|
|
@@ -182,9 +176,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
182
176
|
),
|
|
183
177
|
)
|
|
184
178
|
|
|
185
|
-
parser.add_argument(
|
|
186
|
-
"--delete", action="store_true", help="CSVファイルに記載されていないプロジェクトメンバを削除します。ただし自分自身は削除しません。"
|
|
187
|
-
)
|
|
179
|
+
parser.add_argument("--delete", action="store_true", help="CSVファイルに記載されていないプロジェクトメンバを削除します。ただし自分自身は削除しません。")
|
|
188
180
|
|
|
189
181
|
parser.set_defaults(subcommand_func=main)
|
|
190
182
|
|
|
@@ -70,13 +70,9 @@ def create_replacement_dict(
|
|
|
70
70
|
not_masked_biography_set: マスクしないbiographyの集合。指定したbiographyに該当するユーザーのuser_id,username,account_idはマスクしません。
|
|
71
71
|
"""
|
|
72
72
|
|
|
73
|
-
assert {"user_id", "username", "account_id", "biography"} - set(df_user.columns) == set(),
|
|
74
|
-
"df_userには'user_id','username','account_id','biography'の列が必要です。"
|
|
75
|
-
)
|
|
73
|
+
assert {"user_id", "username", "account_id", "biography"} - set(df_user.columns) == set(), "df_userには'user_id','username','account_id','biography'の列が必要です。"
|
|
76
74
|
|
|
77
|
-
replacement_dict_for_user_id = create_replacement_dict_by_user_id(
|
|
78
|
-
df_user, not_masked_biography_set=not_masked_biography_set, not_masked_user_id_set=not_masked_user_id_set
|
|
79
|
-
)
|
|
75
|
+
replacement_dict_for_user_id = create_replacement_dict_by_user_id(df_user, not_masked_biography_set=not_masked_biography_set, not_masked_user_id_set=not_masked_user_id_set)
|
|
80
76
|
|
|
81
77
|
df2 = df_user.set_index("user_id")
|
|
82
78
|
df3 = df2.loc[replacement_dict_for_user_id.keys()]
|
|
@@ -181,17 +177,13 @@ def mask_visualization_dir(
|
|
|
181
177
|
)
|
|
182
178
|
|
|
183
179
|
# CSVのユーザ情報をマスクする
|
|
184
|
-
masked_user_performance = UserPerformance.from_df_wrapper(
|
|
185
|
-
masked_worktime_per_date, masked_task_worktime_by_phase_user, task_completion_criteria=project_dir.task_completion_criteria
|
|
186
|
-
)
|
|
180
|
+
masked_user_performance = UserPerformance.from_df_wrapper(masked_worktime_per_date, masked_task_worktime_by_phase_user, task_completion_criteria=project_dir.task_completion_criteria)
|
|
187
181
|
output_project_dir.write_user_performance(masked_user_performance)
|
|
188
182
|
|
|
189
183
|
# メンバのパフォーマンスを散布図で出力する
|
|
190
184
|
output_project_dir.write_user_performance_scatter_plot(masked_user_performance)
|
|
191
185
|
|
|
192
|
-
masked_task = project_dir.read_task_list().mask_user_info(
|
|
193
|
-
to_replace_for_user_id=replacement_dict.user_id, to_replace_for_username=replacement_dict.username
|
|
194
|
-
)
|
|
186
|
+
masked_task = project_dir.read_task_list().mask_user_info(to_replace_for_user_id=replacement_dict.user_id, to_replace_for_username=replacement_dict.username)
|
|
195
187
|
output_project_dir.write_task_list(masked_task)
|
|
196
188
|
|
|
197
189
|
write_line_graph(masked_task_worktime_by_phase_user, output_project_dir, minimal_output=minimal_output)
|
|
@@ -226,9 +218,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
226
218
|
not_masked_biography_set = set(get_list_from_args(args.not_masked_biography)) if args.not_masked_biography is not None else None
|
|
227
219
|
not_masked_user_id_set = set(get_list_from_args(args.not_masked_user_id)) if args.not_masked_user_id is not None else None
|
|
228
220
|
|
|
229
|
-
custom_production_volume_list = (
|
|
230
|
-
create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
|
|
231
|
-
)
|
|
221
|
+
custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
|
|
232
222
|
|
|
233
223
|
task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
|
|
234
224
|
input_project_dir = ProjectDir(
|
|
@@ -255,7 +245,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
255
245
|
"--dir",
|
|
256
246
|
type=Path,
|
|
257
247
|
required=True,
|
|
258
|
-
help="マスクしたいプロジェクトディレクトリを指定してください。プロジェクトディレクトリは ``annofabcli statistics visualize`` コマンドの出力結果です。",
|
|
248
|
+
help="マスクしたいプロジェクトディレクトリを指定してください。プロジェクトディレクトリは ``annofabcli statistics visualize`` コマンドの出力結果です。",
|
|
259
249
|
)
|
|
260
250
|
|
|
261
251
|
parser.add_argument(
|
|
@@ -102,15 +102,9 @@ class WritingVisualizationFile:
|
|
|
102
102
|
self.output_project_dir.write_performance_per_started_date_csv(acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE)
|
|
103
103
|
|
|
104
104
|
# 折れ線グラフを出力
|
|
105
|
-
self.output_project_dir.write_performance_line_graph_per_date(
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
self.output_project_dir.write_performance_line_graph_per_date(
|
|
109
|
-
inspector_per_date_obj, phase=TaskPhase.INSPECTION, user_id_list=self.user_id_list
|
|
110
|
-
)
|
|
111
|
-
self.output_project_dir.write_performance_line_graph_per_date(
|
|
112
|
-
acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=self.user_id_list
|
|
113
|
-
)
|
|
105
|
+
self.output_project_dir.write_performance_line_graph_per_date(annotator_per_date_obj, phase=TaskPhase.ANNOTATION, user_id_list=self.user_id_list)
|
|
106
|
+
self.output_project_dir.write_performance_line_graph_per_date(inspector_per_date_obj, phase=TaskPhase.INSPECTION, user_id_list=self.user_id_list)
|
|
107
|
+
self.output_project_dir.write_performance_line_graph_per_date(acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=self.user_id_list)
|
|
114
108
|
|
|
115
109
|
@_catch_exception
|
|
116
110
|
def write_task_list_and_histogram(self, task: Task) -> None:
|
|
@@ -188,9 +182,7 @@ class MergingVisualizationFile:
|
|
|
188
182
|
project_info = project_dir.read_project_info()
|
|
189
183
|
project_info_list.append(project_info)
|
|
190
184
|
|
|
191
|
-
merge_info = MergingInfo(
|
|
192
|
-
target_dir_list=target_dir_list, project_info_list=project_info_list, task_completion_criteria=task_completion_criteria
|
|
193
|
-
)
|
|
185
|
+
merge_info = MergingInfo(target_dir_list=target_dir_list, project_info_list=project_info_list, task_completion_criteria=task_completion_criteria)
|
|
194
186
|
return merge_info
|
|
195
187
|
|
|
196
188
|
|
|
@@ -223,9 +215,7 @@ def merge_visualization_dir( # pylint: disable=too-many-statements
|
|
|
223
215
|
worktime_per_date=worktime_per_date,
|
|
224
216
|
task_completion_criteria=task_completion_criteria,
|
|
225
217
|
)
|
|
226
|
-
writing_obj = WritingVisualizationFile(
|
|
227
|
-
output_project_dir, user_id_list=user_id_list, minimal_output=minimal_output, task_completion_criteria=task_completion_criteria
|
|
228
|
-
)
|
|
218
|
+
writing_obj = WritingVisualizationFile(output_project_dir, user_id_list=user_id_list, minimal_output=minimal_output, task_completion_criteria=task_completion_criteria)
|
|
229
219
|
|
|
230
220
|
writing_obj.write_task_list_and_histogram(task)
|
|
231
221
|
writing_obj.write_worktime_per_date(worktime_per_date)
|
|
@@ -270,9 +260,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
270
260
|
|
|
271
261
|
user_id_list = get_list_from_args(args.user_id) if args.user_id is not None else None
|
|
272
262
|
|
|
273
|
-
custom_production_volume_list = (
|
|
274
|
-
create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
|
|
275
|
-
)
|
|
263
|
+
custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
|
|
276
264
|
task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
|
|
277
265
|
merge_visualization_dir(
|
|
278
266
|
project_dir_list=[ProjectDir(e, task_completion_criteria) for e in args.dir],
|
|
@@ -32,13 +32,9 @@ def create_custom_production_volume_list(cli_value: str) -> list[ProductionVolum
|
|
|
32
32
|
def main(args: argparse.Namespace) -> None:
|
|
33
33
|
root_dir: Path = args.dir
|
|
34
34
|
# task_completion_criteriaは何でもよいので、とりあえずACCEPTANCE_COMPLETEDを指定
|
|
35
|
-
project_dir_list = [
|
|
36
|
-
ProjectDir(elm, task_completion_criteria=TaskCompletionCriteria.ACCEPTANCE_COMPLETED) for elm in root_dir.iterdir() if elm.is_dir()
|
|
37
|
-
]
|
|
35
|
+
project_dir_list = [ProjectDir(elm, task_completion_criteria=TaskCompletionCriteria.ACCEPTANCE_COMPLETED) for elm in root_dir.iterdir() if elm.is_dir()]
|
|
38
36
|
|
|
39
|
-
custom_production_volume_list = (
|
|
40
|
-
create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
|
|
41
|
-
)
|
|
37
|
+
custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
|
|
42
38
|
project_performance = ProjectPerformance.from_project_dirs(project_dir_list, custom_production_volume_list=custom_production_volume_list)
|
|
43
39
|
project_performance.to_csv(args.output)
|
|
44
40
|
|
|
@@ -70,7 +66,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
70
66
|
|
|
71
67
|
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
72
68
|
subcommand_name = "summarize_whole_performance_csv"
|
|
73
|
-
subcommand_help = "``annofabcli statistics visualize`` コマンドの出力結果であるプロジェクトディレクトリから、プロジェクトごとの生産性や品質の一覧を出力します。。"
|
|
69
|
+
subcommand_help = "``annofabcli statistics visualize`` コマンドの出力結果であるプロジェクトディレクトリから、プロジェクトごとの生産性や品質の一覧を出力します。。"
|
|
74
70
|
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help)
|
|
75
71
|
parse_args(parser)
|
|
76
72
|
return parser
|