Skip to content

Commit 2a9bfbc

Browse files
yyassi-heartexAndrejOroscursoragent
authored
fix: BROS-847: Disable Submit/Update/Fix & Accept button when polygon region is unfinished (#9591)
Co-authored-by: AndrejOros <andrej.oros@heartex.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: AndrejOros <AndrejOros@users.noreply.github.com>
1 parent 8ec90c1 commit 2a9bfbc

File tree

4 files changed

+85
-60
lines changed

4 files changed

+85
-60
lines changed

web/libs/editor/src/components/BottomBar/Controls.tsx

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ type ControlButtonProps = {
4444
};
4545

4646
export const EMPTY_SUBMIT_TOOLTIP = "Empty annotations denied in this project";
47+
export const INCOMPLETE_SUBMIT_TOOLTIP = "Complete all regions before submitting";
48+
export const INCOMPLETE_UPDATE_TOOLTIP = "Complete all regions before updating";
49+
export const INCOMPLETE_ACCEPT_TOOLTIP = "Complete all regions before accepting";
4750

4851
/**
4952
* Custom action button component, rendering buttons from store.customButtons
@@ -79,6 +82,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>(
7982
const [isInProgress, setIsInProgress] = useState(false);
8083
const disabled = !annotationEditable || store.isSubmitting || historySelected || isInProgress;
8184
const submitDisabled = store.hasInterface("annotations:deny-empty") && results.length === 0;
85+
const hasIncompleteRegions = annotation.hasIncompletePolygons;
8286

8387
/** Check all things related to comments and then call the action if all is good */
8488
const handleActionWithComments = useCallback(
@@ -197,7 +201,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>(
197201

198202
// Also disable when overlap is reached (only when feature flag is enabled)
199203
const overlapDisabled = isFF(FF_FIT_1304_STRICT_OVERLAP) && store.overlapReached === true;
200-
const isDisabled = disabled || submitDisabled || overlapDisabled;
204+
const isDisabled = disabled || submitDisabled || overlapDisabled || hasIncompleteRegions;
201205

202206
const useExitOption = !isDisabled && isNotQuickView;
203207

@@ -237,14 +241,16 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>(
237241
};
238242

239243
if (userGenerate || (store.explore && !userGenerate && store.hasInterface("submit"))) {
240-
const title = overlapDisabled
241-
? store.overlapReachedMessage
242-
: submitDisabled
243-
? EMPTY_SUBMIT_TOOLTIP
244-
: "Save results: [ Ctrl+Enter ]";
244+
const title = hasIncompleteRegions
245+
? INCOMPLETE_SUBMIT_TOOLTIP
246+
: overlapDisabled
247+
? store.overlapReachedMessage
248+
: submitDisabled
249+
? EMPTY_SUBMIT_TOOLTIP
250+
: "Save results: [ Ctrl+Enter ]";
245251

246252
buttons.push(
247-
<ButtonTooltip key="submit" title={title}>
253+
<ButtonTooltip key="submit" title={title} className="whitespace-nowrap max-w-none">
248254
<div className={cn("controls").elem("tooltip-wrapper").toClassName()}>
249255
<ButtonGroup>
250256
<Button
@@ -291,46 +297,50 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>(
291297
// no changes were made over previously submitted version — no drafts, no pending changes
292298
const noChanges = isFF(FF_REVIEWER_FLOW) && !history.canUndo && !annotation.draftId;
293299
const isUpdateDisabled = isDisabled || noChanges;
294-
const updateTitle = overlapDisabled
295-
? store.overlapReachedMessage
296-
: noChanges
297-
? "No changes were made"
298-
: "Update this task: [ Ctrl+Enter ]";
300+
const updateTitle = hasIncompleteRegions
301+
? INCOMPLETE_UPDATE_TOOLTIP
302+
: overlapDisabled
303+
? store.overlapReachedMessage
304+
: noChanges
305+
? "No changes were made"
306+
: "Update this task: [ Ctrl+Enter ]";
299307
const button = (
300-
<ButtonTooltip key="update" title={updateTitle}>
301-
<ButtonGroup>
302-
<Button
303-
aria-label="submit"
304-
name="submit"
305-
className="w-[150px]"
306-
disabled={isUpdateDisabled}
307-
onClick={async (event) => {
308-
if ((event.target as HTMLButtonElement).classList.contains(dropdownTrigger)) return;
309-
const selected = store.annotationStore?.selected;
310-
311-
selected?.submissionInProgress();
312-
await store.commentStore.commentFormSubmit();
313-
store.updateAnnotation();
314-
}}
315-
data-testid="bottombar-update-button"
316-
>
317-
{isUpdate ? "Update" : "Submit"}
318-
</Button>
319-
{useExitOption ? (
320-
<Dropdown.Trigger
321-
alignment="top-right"
322-
content={<SubmitOption onClickMethod={store.updateAnnotation} isUpdate={isUpdate} />}
308+
<ButtonTooltip key="update" title={updateTitle} className="whitespace-nowrap max-w-none">
309+
<div className={cn("controls").elem("tooltip-wrapper").toClassName()}>
310+
<ButtonGroup>
311+
<Button
312+
aria-label="submit"
313+
name="submit"
314+
className="w-[150px]"
315+
disabled={isUpdateDisabled}
316+
onClick={async (event) => {
317+
if ((event.target as HTMLButtonElement).classList.contains(dropdownTrigger)) return;
318+
const selected = store.annotationStore?.selected;
319+
320+
selected?.submissionInProgress();
321+
await store.commentStore.commentFormSubmit();
322+
store.updateAnnotation();
323+
}}
324+
data-testid="bottombar-update-button"
323325
>
324-
<Button
325-
disabled={isUpdateDisabled}
326-
aria-label="Update annotation"
327-
data-testid="bottombar-update-dropdown"
326+
{isUpdate ? "Update" : "Submit"}
327+
</Button>
328+
{useExitOption ? (
329+
<Dropdown.Trigger
330+
alignment="top-right"
331+
content={<SubmitOption onClickMethod={store.updateAnnotation} isUpdate={isUpdate} />}
328332
>
329-
<IconChevronDown />
330-
</Button>
331-
</Dropdown.Trigger>
332-
) : null}
333-
</ButtonGroup>
333+
<Button
334+
disabled={isUpdateDisabled}
335+
aria-label="Update annotation"
336+
data-testid="bottombar-update-dropdown"
337+
>
338+
<IconChevronDown />
339+
</Button>
340+
</Dropdown.Trigger>
341+
) : null}
342+
</ButtonGroup>
343+
</div>
334344
</ButtonTooltip>
335345
);
336346

web/libs/editor/src/components/BottomBar/buttons.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Tooltip, Button } from "@humansignal/ui";
1111
import { IconInfoOutline } from "@humansignal/icons";
1212
import type { MSTStore } from "../../stores/types";
1313
import { FF_FIT_1304_STRICT_OVERLAP, isFF } from "../../utils/feature-flags";
14+
import { INCOMPLETE_ACCEPT_TOOLTIP } from "./Controls";
1415

1516
type MixedInParams = {
1617
store: MSTStore;
@@ -31,12 +32,13 @@ export function controlsInjector<T extends {}>(fn: (props: T & MixedInParams) =>
3132
type ButtonTooltipProps = {
3233
title: string;
3334
children: JSX.Element;
35+
className?: string;
3436
};
3537

3638
export const ButtonTooltip = controlsInjector<ButtonTooltipProps>(
37-
observer(({ store, title, children }) => {
39+
observer(({ store, title, children, className }) => {
3840
return (
39-
<Tooltip title={title} disabled={!store.settings.enableTooltips}>
41+
<Tooltip title={title} disabled={!store.settings.enableTooltips} className={className}>
4042
{children}
4143
</Tooltip>
4244
);
@@ -54,22 +56,26 @@ export const AcceptButton = memo(
5456
const annotation = store.annotationStore.selected;
5557
// changes in current sessions or saved draft
5658
const hasChanges = history.canUndo || annotation.versions.draft;
59+
const hasIncompleteRegions = annotation.hasIncompletePolygons;
60+
const isDisabled = disabled || hasIncompleteRegions;
61+
const tooltip = hasIncompleteRegions ? INCOMPLETE_ACCEPT_TOOLTIP : "Accept annotation: [ Ctrl+Enter ]";
5762

5863
return (
59-
<Button
60-
key="accept"
61-
tooltip="Accept annotation: [ Ctrl+Enter ]"
62-
aria-label="accept-annotation"
63-
disabled={disabled}
64-
onClick={async () => {
65-
annotation.submissionInProgress();
66-
await store.commentStore.commentFormSubmit();
67-
store.acceptAnnotation();
68-
}}
69-
data-testid="bottombar-accept-button"
70-
>
71-
{hasChanges ? "Fix + Accept" : "Accept"}
72-
</Button>
64+
<Tooltip title={tooltip} disabled={!store.settings.enableTooltips} className="whitespace-nowrap max-w-none">
65+
<Button
66+
key="accept"
67+
aria-label="accept-annotation"
68+
disabled={isDisabled}
69+
onClick={async () => {
70+
annotation.submissionInProgress();
71+
await store.commentStore.commentFormSubmit();
72+
store.acceptAnnotation();
73+
}}
74+
data-testid="bottombar-accept-button"
75+
>
76+
{hasChanges ? "Fix + Accept" : "Accept"}
77+
</Button>
78+
</Tooltip>
7379
);
7480
}),
7581
);

web/libs/editor/src/stores/Annotation/Annotation.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,14 @@ const _Annotation = types
284284
return results;
285285
},
286286

287+
get hasIncompletePolygons() {
288+
if (!isAlive(self)) return false;
289+
for (const area of self.areas.values()) {
290+
if (area.type === "polygonregion" && area.incomplete) return true;
291+
}
292+
return false;
293+
},
294+
287295
get serialized() {
288296
// Dirty hack to force MST track changes
289297
self.areas.toJSON();

web/libs/editor/src/stores/AppStore.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ export default types
368368
if (annotationStore.viewingAll) return;
369369
if (isUpdateDisabled) return;
370370
if (entity.isReadOnly()) return;
371+
if (entity.hasIncompletePolygons) return;
371372

372373
entity?.submissionInProgress();
373374

0 commit comments

Comments
 (0)