From 43c3bb8f059748f89bfc23c968815bdccfd0c7dc Mon Sep 17 00:00:00 2001 From: gferraro Date: Tue, 10 Dec 2024 20:13:10 +1300 Subject: [PATCH] add retry reprocessed endpoint --- api/api/V1/Reprocess.ts | 39 ++++++++++++- api/classifications/classifications.js | 58 +++++++++---------- api/models/Recording.ts | 12 +++- browse/src/api/Recording.api.ts | 6 +- .../src/components/Video/VideoRecording.vue | 2 +- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/api/api/V1/Reprocess.ts b/api/api/V1/Reprocess.ts index b80dc9c8..be4e7800 100755 --- a/api/api/V1/Reprocess.ts +++ b/api/api/V1/Reprocess.ts @@ -28,7 +28,7 @@ import { import { idOf } from "../validation-middleware.js"; import { successResponse } from "./responseUtil.js"; import type { NextFunction } from "express-serve-static-core"; -import { ClientError } from "../customErrors.js"; +import { ClientError, BadRequestError } from "../customErrors.js"; import { arrayOf, jsonSchemaOf } from "../schema-validation.js"; import lodash from "lodash"; import RecordingIdSchema from "@schemas/api/common/RecordingId.schema.json" assert { type: "json" }; @@ -38,6 +38,43 @@ const { uniq: dedupe } = lodash; export default (app: Application, baseUrl: string) => { const apiUrl = `${baseUrl}/reprocess`; + /** + * @api {get} /api/v1/reprocess/retry/:id Retry processing a single recording which is in a failed state + * @apiName Reprocess + * @apiGroup Recordings + * @apiParam {Integer} id of recording to retry + * @apiDescription Retries processing a recording thats in a failed state + * + * @apiUse V1UserAuthorizationHeader + * + * @apiUse V1ResponseSuccess + * @apiUse V1ResponseError + */ + app.get( + `${apiUrl}/:id`, + extractJwtAuthorizedUser, + validateFields([idOf(param("id"))]), + fetchAuthorizedRequiredRecordingById(param("id")), + async (request: Request, response: Response, next) => { + if (!response.locals.recordings.isFailed()) { + return next( + new BadRequestError( + `Recording is not in a failed state '${response.locals.recordings.processingState}'` + ) + ); + } + if (await response.locals.recording.retryProcessing()) { + return successResponse(response, "Recording reprocessed"); + } else { + return next( + new BadRequestError( + `Could not retry processing of recordings ${response.locals.recordings.id}` + ) + ); + } + } + ); + /** * @api {get} /api/v1/reprocess/:id Reprocess a single recording * @apiName Reprocess diff --git a/api/classifications/classifications.js b/api/classifications/classifications.js index 8dccfbf8..75904228 100644 --- a/api/classifications/classifications.js +++ b/api/classifications/classifications.js @@ -1,37 +1,33 @@ import Classifications from "@/classifications/classification.json" assert { type: "json" }; const flattenNodes = (acc, node, parentPath) => { - for (const child of node.children || []) { - acc[child.label] = { - label: child.label, - display: child.display || child.label, - path: `${parentPath}.${child.label}`, - }; - flattenNodes(acc, child, acc[child.label].path); - } - return acc; + for (const child of node.children || []) { + acc[child.label] = { + label: child.label, + display: child.display || child.label, + path: `${parentPath}.${child.label}`, + }; + flattenNodes(acc, child, acc[child.label].path); + } + return acc; }; export const flatClassifications = (() => { - const nodes = flattenNodes({}, Classifications, "all"); - if (nodes.unknown) { - nodes["unidentified"] = nodes["unknown"]; - } - return nodes; + const nodes = flattenNodes({}, Classifications, "all"); + if (nodes.unknown) { + nodes["unidentified"] = nodes["unknown"]; + } + return nodes; })(); -export const displayLabelForClassificationLabel = ( - label, - aiTag = false, - isAudioContext = false -) => { - label = label.toLowerCase(); - if (label === "unclassified") { - return "AI Queued"; - } - if (label === "unidentified" && aiTag) { - return "Unidentified"; - } - const classifications = flatClassifications; - if ((label === "human" || label === "person") && !isAudioContext) { - return "human"; - } - return (classifications[label] && classifications[label].display) || label; +export const displayLabelForClassificationLabel = (label, aiTag = false, isAudioContext = false) => { + label = label.toLowerCase(); + if (label === "unclassified") { + return "AI Queued"; + } + if (label === "unidentified" && aiTag) { + return "Unidentified"; + } + const classifications = flatClassifications; + if ((label === "human" || label === "person") && !isAudioContext) { + return "human"; + } + return (classifications[label] && classifications[label].display) || label; }; diff --git a/api/models/Recording.ts b/api/models/Recording.ts index 02edbbc4..f6ed6d3e 100755 --- a/api/models/Recording.ts +++ b/api/models/Recording.ts @@ -209,7 +209,7 @@ export interface Recording extends Sequelize.Model, ModelCommon { getGroup: () => Promise; getActiveTracksTagsAndTagger: () => Promise; - + retryProcessing: () => Promise; reprocess: () => Promise; filterData: (options: any) => void; // NOTE: Implicitly created by sequelize associations (along with other @@ -787,6 +787,16 @@ from ( }; } + // retry processing this recording + Recording.prototype.retryProcessing = async function () { + if (!this.processingState.endsWith(".failed")) { + return null; + } + await this.update({ + processingState: this.processingState.replace(".failed", ""), + }); + }; + // reprocess a recording and set all active tracks to archived Recording.prototype.reprocess = async function () { const tags = await this.getTags(); diff --git a/browse/src/api/Recording.api.ts b/browse/src/api/Recording.api.ts index d44cbc67..cc1ae1db 100644 --- a/browse/src/api/Recording.api.ts +++ b/browse/src/api/Recording.api.ts @@ -546,8 +546,8 @@ function deleteRecordingTag( return CacophonyApi.delete(`${apiPath}/${id}/tags/${tagId}`); } -function reprocess(id: RecordingId): Promise> { - return CacophonyApi.get(`/api/v1/reprocess/${id}`); +function retryProcessing(id: RecordingId): Promise> { + return CacophonyApi.get(`/api/v1/reprocess/retry/${id}`); } function thumbnail(id: RecordingId): string { @@ -641,7 +641,7 @@ export default { deleteTrack, undeleteTrack, updateTrack, - reprocess, + retryProcessing, addTrackTag, deleteTrackTag, replaceTrackTag, diff --git a/browse/src/components/Video/VideoRecording.vue b/browse/src/components/Video/VideoRecording.vue index bc5777c5..f3262ca3 100644 --- a/browse/src/components/Video/VideoRecording.vue +++ b/browse/src/components/Video/VideoRecording.vue @@ -329,7 +329,7 @@ export default { }, methods: { async reprocess() { - const { success } = await api.recording.reprocess(this.recordingId); + const { success } = await api.recording.retryProcessing(this.recordingId); if (success) { this.$emit("recording-updated", { id: this.recordingId,