Skip to content

Commit

Permalink
Extended client plugins system, refactored job finishing (cvat-ai#8102)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsekachev authored Jul 8, 2024
1 parent 13155bc commit 252b2a7
Show file tree
Hide file tree
Showing 22 changed files with 347 additions and 277 deletions.
4 changes: 4 additions & 0 deletions changelog.d/20240701_131217_boris_immediate_feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Removed

- Renew the job button in annotation menu was removed
(<https://github.com/cvat-ai/cvat/pull/8102>)
4 changes: 4 additions & 0 deletions changelog.d/20240701_131317_boris_immediate_feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Changed

- "Finish the job" button on annotation view now only sets state to 'completed'.
The job stage keeps unchanged (<https://github.com/cvat-ai/cvat/pull/8102>)
4 changes: 4 additions & 0 deletions changelog.d/20240701_131408_boris_immediate_feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Added

- User now may update a job state from the corresponding task page
(<https://github.com/cvat-ai/cvat/pull/8102>)
31 changes: 23 additions & 8 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
RectDrawingMethod, CuboidDrawingMethod, Canvas, CanvasMode as Canvas2DMode,
} from 'cvat-canvas-wrapper';
import {
getCore, MLModel, JobType, Job, QualityConflict, ObjectState,
JobState, JobStage,
getCore, MLModel, JobType, Job,
QualityConflict, ObjectState, JobState,
} from 'cvat-core-wrapper';
import logger, { EventScope } from 'cvat-logger';
import { getCVATStore } from 'cvat-store';
Expand Down Expand Up @@ -1001,7 +1001,6 @@ export function getJobAsync({
export function updateCurrentJobAsync(
jobFieldsToUpdate: {
state?: JobState;
stage?: JobStage;
},
): ThunkAction {
return async (dispatch: ThunkDispatch) => {
Expand All @@ -1019,7 +1018,7 @@ export function updateCurrentJobAsync(
};
}

export function saveAnnotationsAsync(afterSave?: () => void): ThunkAction {
export function saveAnnotationsAsync(): ThunkAction {
return async (dispatch: ThunkDispatch): Promise<void> => {
const { jobInstance } = receiveAnnotationsParameters();

Expand All @@ -1045,10 +1044,6 @@ export function saveAnnotationsAsync(afterSave?: () => void): ThunkAction {
payload: {},
});

if (typeof afterSave === 'function') {
afterSave();
}

dispatch(fetchAnnotationsAsync());
} catch (error) {
dispatch({
Expand All @@ -1057,6 +1052,26 @@ export function saveAnnotationsAsync(afterSave?: () => void): ThunkAction {
error,
},
});

throw error;
}
};
}

export function finishCurrentJobAsync(): ThunkAction {
return async (dispatch: ThunkDispatch, getState) => {
const state = getState();
const beforeCallbacks = state.plugins.callbacks.annotationPage.header.menu.beforeJobFinish;
const { jobInstance } = receiveAnnotationsParameters();

await dispatch(saveAnnotationsAsync());

for await (const callback of beforeCallbacks) {
await callback();
}

if (jobInstance.state !== JobState.COMPLETED) {
await dispatch(updateCurrentJobAsync({ state: JobState.COMPLETED }));
}
};
}
Expand Down
12 changes: 11 additions & 1 deletion cvat-ui/src/actions/plugins-actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (C) 2020-2022 Intel Corporation
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

Expand All @@ -17,6 +17,8 @@ export enum PluginsActionTypes {
ADD_PLUGIN = 'ADD_PLUGIN',
ADD_UI_COMPONENT = 'ADD_UI_COMPONENT',
REMOVE_UI_COMPONENT = 'REMOVE_UI_COMPONENT',
ADD_UI_CALLBACK = 'ADD_UI_CALLBACK',
REMOVE_UI_CALLBACK = 'REMOVE_UI_CALLBACK',
}

export const pluginActions = {
Expand All @@ -39,6 +41,14 @@ export const pluginActions = {
removeUIComponent: (path: string, component: React.Component) => createAction(
PluginsActionTypes.REMOVE_UI_COMPONENT, { path, component },
),
addUICallback: (
path: string,
callback: CallableFunction,
) => createAction(PluginsActionTypes.ADD_UI_CALLBACK, { path, callback }),
removeUICallback: (
path: string,
callback: CallableFunction,
) => createAction(PluginsActionTypes.REMOVE_UI_CALLBACK, { path, callback }),
};

export type PluginActions = ActionUnion<typeof pluginActions>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,9 @@ function AnnotationsActionsModalContent(props: { onClose: () => void; }): JSX.El
className='cvat-action-runner-save-job-recommendation'
type='link'
onClick={() => {
storage.dispatch(
saveAnnotationsAsync(() => {
dispatch(reducerActions.setJobSavedFlag(true));
}),
);
storage.dispatch(saveAnnotationsAsync()).then(() => {
dispatch(reducerActions.setJobSavedFlag(true));
});
}}
>
Click to save the job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import InputNumber from 'antd/lib/input-number';
import Select from 'antd/lib/select';
import Alert from 'antd/lib/alert';
import Modal from 'antd/lib/modal';
import Button from 'antd/lib/button';
import message from 'antd/lib/message';

import { CombinedState, NavigationType, ObjectType } from 'reducers';
import { Canvas, CanvasMode } from 'cvat-canvas-wrapper';
import {
Job, JobState, Label, LabelType,
} from 'cvat-core-wrapper';
import { Job, Label, LabelType } from 'cvat-core-wrapper';
import { ActionUnion, createAction } from 'utils/redux';
import {
rememberObject, changeFrameAsync, saveAnnotationsAsync, setNavigationType,
removeObjectAsync, updateCurrentJobAsync,
rememberObject, changeFrameAsync, setNavigationType,
removeObjectAsync, finishCurrentJobAsync,
} from 'actions/annotation-actions';
import LabelSelector from 'components/label-selector/label-selector';
import GlobalHotKeys from 'utils/mousetrap-react';
Expand All @@ -47,15 +45,6 @@ function cancelCurrentCanvasOp(state: CombinedState): void {
}
}

function showSubmittedInfo(): void {
Modal.info({
closable: false,
title: 'Annotations submitted',
content: 'You may close the window',
className: 'cvat-single-shape-annotation-submit-success-modal',
});
}

function makeMessage(label: Label, labelType: State['labelType'], pointsCount: number): JSX.Element {
let readableShape = '';
if (labelType === LabelType.POINTS) {
Expand All @@ -77,8 +66,8 @@ function makeMessage(label: Label, labelType: State['labelType'], pointsCount: n
}

export const actionCreators = {
switchAutoNextFrame: () => (
createAction(ReducerActionType.SWITCH_AUTO_NEXT_FRAME)
switchAutoNextFrame: (autoNextFrame: boolean) => (
createAction(ReducerActionType.SWITCH_AUTO_NEXT_FRAME, { autoNextFrame })
),
switchAutoSaveOnFinish: () => (
createAction(ReducerActionType.SWITCH_AUTOSAVE_ON_FINISH)
Expand Down Expand Up @@ -129,7 +118,7 @@ const reducer = (state: State, action: ActionUnion<typeof actionCreators>): Stat
if (action.type === ReducerActionType.SWITCH_AUTO_NEXT_FRAME) {
return {
...state,
autoNextFrame: !state.autoNextFrame,
autoNextFrame: action.payload.autoNextFrame,
};
}

Expand Down Expand Up @@ -236,40 +225,41 @@ function SingleShapeSidebar(): JSX.Element {

const getNextFrame = useCallback(() => {
if (frame + 1 > jobInstance.stopFrame) {
return Promise.resolve(null);
dispatch(actionCreators.setNextFrame(null));
return;
}

return jobInstance.annotations.search(frame + 1, jobInstance.stopFrame, {
jobInstance.annotations.search(frame + 1, jobInstance.stopFrame, {
allowDeletedFrames: false,
...(navigationType === NavigationType.EMPTY ? {
generalFilters: {
isEmptyFrame: true,
},
} : {}),
}) as Promise<number | null>;
}, [jobInstance, navigationType, frame]);
}).then((_frame: number | null) => {
dispatch(actionCreators.setNextFrame(_frame));
});
// implicitly depends on annotations because may use notEmpty filter
}, [jobInstance, navigationType, frame, annotations]);

const finishOnThisFrame = useCallback((forceSave = false): void => {
if (typeof state.nextFrame === 'number') {
appDispatch(changeFrameAsync(state.nextFrame));
} else if ((forceSave || state.saveOnFinish) && !savingRef.current) {
savingRef.current = true;

const patchJob = (): void => {
appDispatch(updateCurrentJobAsync({
state: JobState.COMPLETED,
})).then(showSubmittedInfo).finally(() => {
savingRef.current = false;
appDispatch(finishCurrentJobAsync()).then(() => {
message.open({
duration: 1,
type: 'success',
content: 'You tagged the job as completed',
className: 'cvat-annotation-job-finished-success',
});
};

if (jobInstance.annotations.hasUnsavedChanges()) {
appDispatch(saveAnnotationsAsync(patchJob)).catch(() => {
savingRef.current = false;
});
} else {
patchJob();
}
}).finally(() => {
appDispatch(setNavigationType(NavigationType.REGULAR));
dispatch(actionCreators.switchAutoNextFrame(false));
savingRef.current = false;
});
}
}, [state.saveOnFinish, state.nextFrame, jobInstance]);

Expand All @@ -292,9 +282,7 @@ function SingleShapeSidebar(): JSX.Element {
}, []);

useEffect(() => {
getNextFrame().then((_frame: number | null) => {
dispatch(actionCreators.setNextFrame(_frame));
});
getNextFrame();
}, [getNextFrame]);

useEffect(() => {
Expand Down Expand Up @@ -540,7 +528,7 @@ function SingleShapeSidebar(): JSX.Element {
checked={state.autoNextFrame}
onChange={(): void => {
(window.document.activeElement as HTMLInputElement)?.blur();
dispatch(actionCreators.switchAutoNextFrame());
dispatch(actionCreators.switchAutoNextFrame(!state.autoNextFrame));
}}
>
Automatically go to the next frame
Expand Down
Loading

0 comments on commit 252b2a7

Please sign in to comment.