From f5dae55b39f040b314072b14308f5ecc1d8f6694 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Wed, 27 Mar 2024 13:43:00 +0300 Subject: [PATCH] Fixed duplicated notifications for automatic annotation (#7595) --- ...akhov_fix_auto_annotation_notifications.md | 4 +++ cvat-core/src/lambda-manager.ts | 2 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/models-actions.ts | 30 ++++++++++++----- cvat-ui/src/reducers/index.ts | 3 ++ cvat-ui/src/reducers/models-reducer.ts | 33 ++++++++++++++----- 6 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 changelog.d/20240312_155103_klakhov_fix_auto_annotation_notifications.md diff --git a/changelog.d/20240312_155103_klakhov_fix_auto_annotation_notifications.md b/changelog.d/20240312_155103_klakhov_fix_auto_annotation_notifications.md new file mode 100644 index 000000000000..7412279e9a6c --- /dev/null +++ b/changelog.d/20240312_155103_klakhov_fix_auto_annotation_notifications.md @@ -0,0 +1,4 @@ +### Fixed + +- Duplicated notifications for automatic annotation + () diff --git a/cvat-core/src/lambda-manager.ts b/cvat-core/src/lambda-manager.ts index c0c41cb63288..39301cf853ac 100644 --- a/cvat-core/src/lambda-manager.ts +++ b/cvat-core/src/lambda-manager.ts @@ -106,7 +106,7 @@ class LambdaManager { async listen( requestID: string, - functionID: string, + functionID: string | number, callback: (status: RQStatus, progress: number, message?: string) => void, ): Promise { const model = this.cachedList.find((_model) => _model.id === functionID); diff --git a/cvat-ui/package.json b/cvat-ui/package.json index f4a37c7089ca..546d3f7f5071 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.63.3", + "version": "1.63.4", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index 233751b0f3d3..38a991816234 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022-2023 CVAT.ai Corporation +// Copyright (C) 2022-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -20,6 +20,7 @@ export enum ModelsActionTypes { DELETE_MODEL = 'DELETE_MODEL', DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS', DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED', + GET_INFERENCES_SUCCESS = 'GET_INFERENCES_SUCCESS', START_INFERENCE_FAILED = 'START_INFERENCE_FAILED', GET_INFERENCE_STATUS_SUCCESS = 'GET_INFERENCE_STATUS_SUCCESS', GET_INFERENCE_STATUS_FAILED = 'GET_INFERENCE_STATUS_FAILED', @@ -45,6 +46,9 @@ export const modelsActions = { error, }), fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }), + getInferencesSuccess: (requestedInferenceIDs: Record) => ( + createAction(ModelsActionTypes.GET_INFERENCES_SUCCESS, { requestedInferenceIDs }) + ), getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => ( createAction(ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, { taskID, @@ -64,9 +68,10 @@ export const modelsActions = { error, }) ), - cancelInferenceSuccess: (taskID: number) => ( + cancelInferenceSuccess: (taskID: number, activeInference: ActiveInference) => ( createAction(ModelsActionTypes.CANCEL_INFERENCE_SUCCESS, { taskID, + activeInference, }) ), cancelInferenceFailed: (taskID: number, error: any) => ( @@ -119,8 +124,9 @@ interface InferenceMeta { function listen(inferenceMeta: InferenceMeta, dispatch: (action: ModelsActions) => void): void { const { taskID, requestID, functionID } = inferenceMeta; + core.lambda - .listen(requestID, functionID, (status: RQStatus, progress: number, message: string) => { + .listen(requestID, functionID, (status: RQStatus, progress: number, message?: string) => { if (status === RQStatus.FAILED || status === RQStatus.UNKNOWN) { dispatch( modelsActions.getInferenceStatusFailed( @@ -129,7 +135,7 @@ function listen(inferenceMeta: InferenceMeta, dispatch: (action: ModelsActions) status, progress, functionID, - error: message, + error: message as string, id: requestID, }, new Error(`Inference status for the task ${taskID} is ${status}. ${message}`), @@ -144,7 +150,7 @@ function listen(inferenceMeta: InferenceMeta, dispatch: (action: ModelsActions) status, progress, functionID, - error: message, + error: message as string, id: requestID, }), ); @@ -163,13 +169,16 @@ function listen(inferenceMeta: InferenceMeta, dispatch: (action: ModelsActions) } export function getInferenceStatusAsync(): ThunkAction { - return async (dispatch): Promise => { + return async (dispatch, getState): Promise => { const dispatchCallback = (action: ModelsActions): void => { dispatch(action); }; + const { requestedInferenceIDs } = getState().models; + try { const requests = await core.lambda.requests(); + const newListenedIDs: Record = {}; requests .map((request: any): object => ({ taskID: +request.function.task, @@ -177,8 +186,12 @@ export function getInferenceStatusAsync(): ThunkAction { functionID: request.function.id, })) .forEach((inferenceMeta: InferenceMeta): void => { - listen(inferenceMeta, dispatchCallback); + if (!(inferenceMeta.requestID in requestedInferenceIDs)) { + listen(inferenceMeta, dispatchCallback); + newListenedIDs[inferenceMeta.requestID] = true; + } }); + dispatch(modelsActions.getInferencesSuccess(newListenedIDs)); } catch (error) { dispatch(modelsActions.fetchMetaFailed(error)); } @@ -201,6 +214,7 @@ export function startInferenceAsync(taskId: number, model: MLModel, body: object }, dispatchCallback, ); + dispatch(modelsActions.getInferencesSuccess({ [requestID]: true })); } catch (error) { dispatch(modelsActions.startInferenceFailed(taskId, error)); } @@ -212,7 +226,7 @@ export function cancelInferenceAsync(taskID: number): ThunkAction { try { const inference = getState().models.inferences[taskID]; await core.lambda.cancel(inference.id, inference.functionID); - dispatch(modelsActions.cancelInferenceSuccess(taskID)); + dispatch(modelsActions.cancelInferenceSuccess(taskID, inference)); } catch (error) { dispatch(modelsActions.cancelInferenceFailed(taskID, error)); } diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 69017ac736ca..7e092e22ec34 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -414,6 +414,9 @@ export interface ModelsState { reid: MLModel[]; classifiers: MLModel[]; totalCount: number; + requestedInferenceIDs: { + [index: string]: boolean; + }; inferences: { [index: number]: ActiveInference; }; diff --git a/cvat-ui/src/reducers/models-reducer.ts b/cvat-ui/src/reducers/models-reducer.ts index 1a9b11456296..3c7fa6292bae 100644 --- a/cvat-ui/src/reducers/models-reducer.ts +++ b/cvat-ui/src/reducers/models-reducer.ts @@ -1,8 +1,9 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022-2023 CVAT.ai Corporation +// Copyright (C) 2022-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT +import { omit } from 'lodash'; import { BoundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions'; import { ModelsActionTypes, ModelsActions } from 'actions/models-actions'; import { AuthActionTypes, AuthActions } from 'actions/auth-actions'; @@ -20,6 +21,7 @@ const defaultState: ModelsState = { classifiers: [], modelRunnerIsVisible: false, modelRunnerTask: null, + requestedInferenceIDs: {}, inferences: {}, totalCount: 0, query: { @@ -88,15 +90,28 @@ export default function (state = defaultState, action: ModelsActions | AuthActio modelRunnerTask: null, }; } + case ModelsActionTypes.GET_INFERENCES_SUCCESS: { + const { requestedInferenceIDs } = state; + + return { + ...state, + requestedInferenceIDs: { + ...requestedInferenceIDs, + ...action.payload.requestedInferenceIDs, + }, + }; + } case ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS: { - const { inferences } = state; + const { inferences, requestedInferenceIDs } = state; if (action.payload.activeInference.status === 'finished') { + const { taskID, activeInference } = action.payload; + const { id: inferenceID } = activeInference; + return { ...state, - inferences: Object.fromEntries( - Object.entries(inferences).filter(([key]): boolean => +key !== action.payload.taskID), - ), + inferences: omit(inferences, taskID), + requestedInferenceIDs: omit(requestedInferenceIDs, inferenceID), }; } @@ -123,12 +138,14 @@ export default function (state = defaultState, action: ModelsActions | AuthActio }; } case ModelsActionTypes.CANCEL_INFERENCE_SUCCESS: { - const { inferences } = state; - delete inferences[action.payload.taskID]; + const { inferences, requestedInferenceIDs } = state; + const { taskID, activeInference } = action.payload; + const { id: inferenceID } = activeInference; return { ...state, - inferences: { ...inferences }, + inferences: omit(inferences, taskID), + requestedInferenceIDs: omit(requestedInferenceIDs, inferenceID), }; } case ModelsActionTypes.GET_MODEL_PREVIEW: {