diff --git a/backend/data_export/celery_tasks.py b/backend/data_export/celery_tasks.py index f1d315c28d..90ba6258fc 100644 --- a/backend/data_export/celery_tasks.py +++ b/backend/data_export/celery_tasks.py @@ -24,12 +24,16 @@ logger = get_task_logger(__name__) -def create_collaborative_dataset(project: Project, dirpath: str, confirmed_only: bool, formatters, writer): +def create_collaborative_dataset( + project: Project, dirpath: str, confirmed_only: bool, formatters, writer, example_ids=None +): is_text_project = project.is_text_project if confirmed_only: examples = ExportedExample.objects.confirmed(project) else: examples = ExportedExample.objects.filter(project=project) + if example_ids: + examples = examples.filter(id__in=example_ids) labels = create_labels(project, examples) comments = create_comment(examples) dataset = Dataset(examples, labels, comments, is_text_project) @@ -40,7 +44,9 @@ def create_collaborative_dataset(project: Project, dirpath: str, confirmed_only: service.export(filepath) -def create_individual_dataset(project: Project, dirpath: str, confirmed_only: bool, formatters, writer): +def create_individual_dataset( + project: Project, dirpath: str, confirmed_only: bool, formatters, writer, example_ids=None +): is_text_project = project.is_text_project members = Member.objects.filter(project=project) for member in members: @@ -48,6 +54,8 @@ def create_individual_dataset(project: Project, dirpath: str, confirmed_only: bo examples = ExportedExample.objects.confirmed(project, user=member.user) else: examples = ExportedExample.objects.filter(project=project) + if example_ids: + examples = examples.filter(id__in=example_ids) labels = create_labels(project, examples, member.user) comments = create_comment(examples, member.user) dataset = Dataset(examples, labels, comments, is_text_project) @@ -69,7 +77,7 @@ def create_individual_dataset(project: Project, dirpath: str, confirmed_only: bo retry_backoff_max=30, max_retries=3, ) -def export_dataset(project_id, file_format: str, confirmed_only=False): +def export_dataset(project_id, file_format: str, confirmed_only=False, example_ids=None): try: project = get_object_or_404(Project, pk=project_id) dirpath = os.path.join(settings.MEDIA_ROOT, str(uuid.uuid4())) @@ -77,9 +85,9 @@ def export_dataset(project_id, file_format: str, confirmed_only=False): formatters = create_formatter(project, file_format) writer = create_writer(file_format) if project.collaborative_annotation: - create_collaborative_dataset(project, dirpath, confirmed_only, formatters, writer) + create_collaborative_dataset(project, dirpath, confirmed_only, formatters, writer, example_ids) else: - create_individual_dataset(project, dirpath, confirmed_only, formatters, writer) + create_individual_dataset(project, dirpath, confirmed_only, formatters, writer, example_ids) zip_file = shutil.make_archive(dirpath, "zip", dirpath) shutil.rmtree(dirpath) return zip_file diff --git a/backend/data_export/views.py b/backend/data_export/views.py index 9a6610e6d6..9ca579e35e 100644 --- a/backend/data_export/views.py +++ b/backend/data_export/views.py @@ -39,7 +39,12 @@ def post(self, request, *args, **kwargs): project_id = self.kwargs["project_id"] file_format = request.data.pop("format") export_approved = request.data.pop("exportApproved", False) + example_ids = request.data.pop("ids", None) task = export_dataset.delay( - project_id=project_id, file_format=file_format, confirmed_only=export_approved, **request.data + project_id=project_id, + file_format=file_format, + confirmed_only=export_approved, + example_ids=example_ids, + **request.data, ) return Response({"task_id": task.task_id}) diff --git a/frontend/pages/projects/_id/dataset/export.vue b/frontend/pages/projects/_id/dataset/export.vue index 3cd95c3030..2366073a21 100644 --- a/frontend/pages/projects/_id/dataset/export.vue +++ b/frontend/pages/projects/_id/dataset/export.vue @@ -72,6 +72,18 @@ export default Vue.extend({ example(): string { const item = this.formats.find((item: Format) => item.name === this.selectedFormat) return item!.example.trim() + }, + + // Ids of the examples selected on the dataset list page, if any were passed along + selectedIds(): number[] { + const ids = this.$route.query.ids + if (!ids) { + return [] + } + return String(ids) + .split(',') + .map((x) => Number(x.trim())) + .filter((n) => Number.isFinite(n)) } }, @@ -98,7 +110,8 @@ export default Vue.extend({ this.taskId = await this.$repositories.download.prepare( this.projectId, this.selectedFormat, - this.exportApproved + this.exportApproved, + this.selectedIds ) this.pollData() }, diff --git a/frontend/pages/projects/_id/dataset/index.vue b/frontend/pages/projects/_id/dataset/index.vue index be1667868d..519823a65c 100644 --- a/frontend/pages/projects/_id/dataset/index.vue +++ b/frontend/pages/projects/_id/dataset/index.vue @@ -5,7 +5,7 @@ @@ -238,6 +238,15 @@ export default Vue.extend({ this.$router.push(query) }, + // Navigate to the export page, carrying over the selected example ids (if any) + goToExport() { + const query: Record = {} + if (this.selected.length > 0) { + query.ids = this.selected.map((item) => item.id).join(',') + } + this.$router.push({ path: 'dataset/export', query }) + }, + /** * Navigate to the labeling/annotation page. * getLinkToAnnotationPage() builds the correct route for the project's type. diff --git a/frontend/repositories/download/apiDownloadRepository.ts b/frontend/repositories/download/apiDownloadRepository.ts index 60fe3e2180..2ac4e7ea6c 100644 --- a/frontend/repositories/download/apiDownloadRepository.ts +++ b/frontend/repositories/download/apiDownloadRepository.ts @@ -3,12 +3,20 @@ import ApiService from '@/services/api.service' export class APIDownloadRepository { constructor(private readonly request = ApiService) {} - async prepare(projectId: string, format: string, exportApproved: boolean): Promise { + async prepare( + projectId: string, + format: string, + exportApproved: boolean, + ids?: number[] + ): Promise { const url = `/projects/${projectId}/download` - const data = { + const data: { format: string; exportApproved: boolean; ids?: number[] } = { format, exportApproved } + if (ids && ids.length > 0) { + data.ids = ids + } const response = await this.request.post(url, data) return response.data.task_id }