Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions backend/api/views/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ class DownloadViewSet(ViewSet):
url_path="analysis-export/(?P<pk>[^/.]+)",
url_name="analysis-export",
)
def download_analysis_export(self, request, pk=None):
def download_analysis_export(self, _request, pk=None):
"""
Download analysis export
For legacy analysis: audio metadata and spectro config csv
Expand Down Expand Up @@ -431,7 +431,7 @@ def download_phase_annotations(self, request, pk=None):
campaign = phase.annotation_campaign

response = HttpResponse(content_type="text/csv")
filename = f"{campaign.name.replace(' ', '_')}_status.csv"
filename = f"{campaign.name.replace(' ', '_')}_{AnnotationPhase.Type(phase.phase).label}_annotations.csv"
response["Content-Disposition"] = f'attachment; filename="{filename}"'

validate_users = list(
Expand All @@ -451,7 +451,7 @@ def download_phase_annotations(self, request, pk=None):
writer = csv.DictWriter(response, fieldnames=headers)
writer.writeheader()

def map_validations(user: str) -> [str, Case]:
def map_validations(user: str) -> tuple[str, Case]:
validation_sub = AnnotationValidation.objects.filter(
annotator__username=user,
annotation_id=OuterRef("id"),
Expand All @@ -465,7 +465,7 @@ def map_validations(user: str) -> [str, Case]:
default=None,
output_field=models.BooleanField(null=True),
)
return [user, query]
return user, query

results = (
_get_annotations_for_report(phase)
Expand Down Expand Up @@ -497,7 +497,7 @@ def download_phase_progression(self, request, pk=None):
campaign = phase.annotation_campaign

response = HttpResponse(content_type="text/csv")
filename = f"{campaign.name.replace(' ', '_')}_status.csv"
filename = f"{campaign.name.replace(' ', '_')}_{AnnotationPhase.Type(phase.phase).label}_status.csv"
response["Content-Disposition"] = f'attachment; filename="{filename}"'

# Headers
Expand All @@ -521,7 +521,7 @@ def download_phase_progression(self, request, pk=None):
status=AnnotationTask.Status.FINISHED,
)

def map_annotators(user: str) -> [str, Case]:
def map_annotators(user: str) -> tuple[str, Case]:
task_sub = finished_tasks.filter(
spectrogram_id=OuterRef("pk"), annotator__username=user
)
Expand All @@ -536,7 +536,7 @@ def map_annotators(user: str) -> [str, Case]:
default=models.Value("UNASSIGNED"),
output_field=models.CharField(),
)
return [user, query]
return user, query

data = dict(map(map_annotators, annotators))

Expand Down
52 changes: 26 additions & 26 deletions frontend/src/api/download/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@ import type { SpectrogramAnalysisNode } from '@/api/types.gql-generated';


export const DownloadRestAPI = restAPI.injectEndpoints({
endpoints: builder => ({
endpoints: builder => ({

downloadAnalysis: builder.mutation<void, Pick<SpectrogramAnalysisNode, 'id' | 'name'>>({
query: ({ id, name }) => {
return {
url: `/api/download/analysis-export/${ id }/`,
responseHandler: getDownloadResponseHandler(`${ name }.zip`),
}
},
}),
downloadAnalysis: builder.mutation<void, Pick<SpectrogramAnalysisNode, 'id' | 'name'>>({
query: ({ id, name }) => {
return {
url: `/api/download/analysis-export/${ id }/`,
responseHandler: getDownloadResponseHandler(`${ name }.zip`),
}
},
}),

downloadAnnotations: builder.mutation<void, { phaseID: string, campaignName: string }>({
query: ({ phaseID, campaignName }) => {
return {
url: `/api/download/phase-annotations/${ phaseID }/`,
responseHandler: getDownloadResponseHandler(`${ campaignName.replaceAll(' ', '_') }_results.csv`),
}
},
}),
downloadAnnotations: builder.mutation<void, { phaseID: string }>({
query: ({ phaseID }) => {
return {
url: `/api/download/phase-annotations/${ phaseID }/`,
responseHandler: getDownloadResponseHandler(),
}
},
}),

downloadProgress: builder.mutation<void, { phaseID: string, campaignName: string }>({
query: ({ phaseID, campaignName }) => {
return {
url: `/api/download/phase-progression/${ phaseID }/`,
responseHandler: getDownloadResponseHandler(`${ campaignName.replaceAll(' ', '_') }_status.csv`),
}
},
}),
downloadProgress: builder.mutation<void, { phaseID: string }>({
query: ({ phaseID }) => {
return {
url: `/api/download/phase-progression/${ phaseID }/`,
responseHandler: getDownloadResponseHandler(),
}
},
}),

}),
}),
})
50 changes: 23 additions & 27 deletions frontend/src/api/download/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,38 @@ import { useCallback } from 'react';
import { useLoaderData } from '@tanstack/react-router';

const {
downloadAnalysis,
downloadAnnotations,
downloadProgress,
downloadAnalysis,
downloadAnnotations,
downloadProgress,
} = DownloadRestAPI.endpoints


export const useDownloadAnalysis = downloadAnalysis.useMutation

export const useDownloadAnnotations = () => {
const { campaign } = useLoaderData({ from: '/_authenticated/annotation-campaign/$campaignID' })
const { phase } = useLoaderData({ from: '/_authenticated/annotation-campaign/$campaignID/_detailLayout/phase/$phaseType' })
const [ method, info ] = downloadAnnotations.useMutation()
const { phase } = useLoaderData({ from: '/_authenticated/annotation-campaign/$campaignID/_detailLayout/phase/$phaseType' })
const [ method, info ] = downloadAnnotations.useMutation()

return {
downloadAnnotations: useCallback(() => {
return method({
phaseID: phase.id,
campaignName: campaign.name,
}).unwrap()
}, [ method, campaign, phase ]),
...info,
}
return {
downloadAnnotations: useCallback(() => {
return method({
phaseID: phase.id,
}).unwrap()
}, [ method, phase ]),
...info,
}
}

export const useDownloadProgress = () => {
const { campaign } = useLoaderData({ from: '/_authenticated/annotation-campaign/$campaignID' })
const { phase } = useLoaderData({ from: '/_authenticated/annotation-campaign/$campaignID/_detailLayout/phase/$phaseType' })
const [ method, info ] = downloadProgress.useMutation()
const { phase } = useLoaderData({ from: '/_authenticated/annotation-campaign/$campaignID/_detailLayout/phase/$phaseType' })
const [ method, info ] = downloadProgress.useMutation()

return {
downloadProgress: useCallback(() => {
return method({
phaseID: phase.id,
campaignName: campaign.name,
}).unwrap()
}, [ method, campaign, phase ]),
...info,
}
return {
downloadProgress: useCallback(() => {
return method({
phaseID: phase.id,
}).unwrap()
}, [ method, phase ]),
...info,
}
}
12 changes: 10 additions & 2 deletions frontend/src/service/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,22 @@ function downloadFile(filename: string, type: string, blob: Blob) {
a.click();
}

export async function downloadResponseHandler(response: Response, filename: string) {
export async function downloadResponseHandler(response: Response, filename?: string) {
// TODO: reject errors correctly (catchable) - like a standard API error
console.debug(response.headers)
if (response.status !== 200) return `[${ response.status }] ${ response.statusText }`;
const type = response.headers.get('content-type')
if (!type) throw new Error('No file type provided')
if (!filename) {
const contentDispositionHeader = response.headers.get('content-disposition')
if (!contentDispositionHeader) throw new Error('No Content-Disposition header')
const filenameRegExp = new RegExp(/filename="(\S*)"/g).exec(contentDispositionHeader)
if (!filenameRegExp) throw new Error('No filename in Content-Disposition header')
filename = filenameRegExp[1]
}
downloadFile(filename, type, await response.blob())
}

export function getDownloadResponseHandler(filename: string) {
export function getDownloadResponseHandler(filename?: string) {
return (response: Response) => downloadResponseHandler(response, filename)
}
Loading