Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: LEAP-1351: LSF customization for bulk annotation #6203

Merged
merged 29 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d7331f8
feat: LEAP-1351: LSF customization for bulk annotation
Gondragos Aug 14, 2024
6593f92
feat: LEAP-1351: Add ability to customize bottom bar control buttons
Gondragos Aug 14, 2024
16c2bb7
tests: LEAP-1351: Add tests
Gondragos Aug 14, 2024
224514b
Refactoring
Gondragos Aug 14, 2024
9895956
Merge branch 'refs/heads/develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Aug 14, 2024
2b56bb6
biome fixes
Gondragos Aug 14, 2024
e8e756e
Refactoring
Gondragos Aug 14, 2024
d989d4a
Rename interface
Gondragos Aug 14, 2024
73277b6
Hide unnecessary elements for both modes with outliner
Gondragos Aug 14, 2024
0d0842b
Rename variable
Gondragos Aug 14, 2024
f74f9c0
Fix misprint
Gondragos Aug 14, 2024
f19a06f
Hide more things and update tests
Gondragos Aug 14, 2024
f5336ed
Refactor condition
Gondragos Aug 26, 2024
17cb58d
Merge branch 'develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Aug 26, 2024
6ea5746
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Aug 29, 2024
8e51cea
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Sep 2, 2024
62b486f
Merge branch 'develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Nov 20, 2024
f958929
Adjust types, small fixes, add ability to update button
Gondragos Nov 21, 2024
a1bb31d
Adjust tests
Gondragos Nov 21, 2024
d603916
Merge branch 'develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Nov 21, 2024
46427e0
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Nov 22, 2024
1edc48a
Remove unused parts of code
Gondragos Nov 22, 2024
0c6e6e4
Change way of applying state to CustomButton
Gondragos Nov 22, 2024
5e95fbd
Refactoring
Gondragos Nov 22, 2024
bd5b0ed
Merge branch 'fb-leap-1351/bulk-annotation-lsf' of ssh://github.com/h…
Gondragos Nov 22, 2024
117adeb
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Nov 25, 2024
7bfea4b
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Nov 26, 2024
a2a9c93
Apply suggestions from code review
Gondragos Nov 27, 2024
a7f7eda
Refactoring
Gondragos Dec 4, 2024
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
33 changes: 22 additions & 11 deletions web/libs/editor/src/components/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import "../../tags/visual";
*/
import { Space } from "../../common/Space/Space";
import { Button } from "../../common/Button/Button";
import { Block, cn, Elem } from "../../utils/bem";
import { Block, Elem } from "../../utils/bem";
import {
FF_BULK_ANNOTATION,
FF_DEV_1170,
FF_DEV_3873,
FF_LSDV_4620_3_ML,
Expand Down Expand Up @@ -237,6 +238,7 @@ class App extends Component {
</Block>
);

const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");
const outlinerEnabled = isFF(FF_DEV_1170);
const newUIEnabled = isFF(FF_DEV_3873);

Expand Down Expand Up @@ -278,16 +280,25 @@ class App extends Component {
>
{outlinerEnabled ? (
newUIEnabled ? (
<SideTabsPanels
panelsHidden={viewingAll}
currentEntity={as.selectedHistory ?? as.selected}
regions={as.selected.regionStore}
showComments={store.hasInterface("annotations:comments")}
focusTab={store.commentStore.tooltipMessage ? "comments" : null}
>
{mainContent}
{store.hasInterface("topbar") && <BottomBar store={store} />}
</SideTabsPanels>
isBulkMode ? (
<>
{mainContent}
{store.hasInterface("topbar") && <BottomBar store={store} />}
</>
) : (
<SideTabsPanels
panelsHidden={viewingAll}
currentEntity={as.selectedHistory ?? as.selected}
regions={as.selected.regionStore}
showComments={store.hasInterface("annotations:comments")}
focusTab={store.commentStore.tooltipMessage ? "comments" : null}
>
{mainContent}
{store.hasInterface("topbar") && <BottomBar store={store} />}
</SideTabsPanels>
)
) : isBulkMode ? (
<>{mainContent}</>
) : (
<SidePanels
panelsHidden={viewingAll}
Expand Down
4 changes: 3 additions & 1 deletion web/libs/editor/src/components/BottomBar/Actions.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IconInfoOutline, LsSettingsAlt } from "../../assets/icons";
import { Button } from "../../common/Button/Button";
import { Elem } from "../../utils/bem";
import { FF_BULK_ANNOTATION } from "../../utils/feature-flags";
import { EditingHistory } from "./HistoryActions";
import { DynamicPreannotationsToggle } from "../AnnotationTab/DynamicPreannotationsToggle";
import { AutoAcceptToggle } from "../AnnotationTab/AutoAcceptToggle";
Expand All @@ -12,6 +13,7 @@ export const Actions = ({ store }) => {
const entity = annotationStore.selected;
const isPrediction = entity?.type === "prediction";
const isViewAll = annotationStore.viewingAll === true;
const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");

return (
<Elem name="section">
Expand Down Expand Up @@ -46,7 +48,7 @@ export const Actions = ({ store }) => {
/>
</Tooltip>

{store.hasInterface("ground-truth") && <GroundTruth entity={entity} />}
{store.hasInterface("ground-truth") && !isBulkMode && <GroundTruth entity={entity} />}

{!isViewAll && (
<Elem name="section">
Expand Down
10 changes: 3 additions & 7 deletions web/libs/editor/src/components/BottomBar/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
*/

import { observer } from "mobx-react";
import type { Instance } from "mobx-state-tree";
import type React from "react";
import { useCallback, useState } from "react";

import { IconBan, LsChevron } from "../../assets/icons";
import { Button } from "../../common/Button/Button";
import { Dropdown } from "../../common/Dropdown/Dropdown";
import type { CustomButton } from "../../stores/CustomButton";
import type { CustomButtonType } from "../../stores/CustomButton";
import { Block, cn, Elem } from "../../utils/bem";
import { FF_REVIEWER_FLOW, isFF } from "../../utils/feature-flags";
import { isDefined, toArray } from "../../utils/utilities";
Expand All @@ -27,7 +26,6 @@ import {

import "./Controls.scss";

type CustomButtonType = Instance<typeof CustomButton>;
// these buttons can be reused inside custom buttons or can be replaces with custom buttons
type SupportedInternalButtons = "accept" | "reject";
// special places for custom buttons — before, after or instead of internal buttons
Expand Down Expand Up @@ -132,7 +130,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>(
key={customButton.name}
disabled={disabled}
button={customButton}
onClick={() => store.handleCustomButton?.(customButton.name)}
onClick={() => store.handleCustomButton?.(customButton)}
/>,
);
}
Expand All @@ -151,9 +149,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>(
: [originalRejectButton];

rejectButtons.forEach((button) => {
const action = hasCustomReject
? () => store.handleCustomButton?.(button.name)
: () => store.rejectAnnotation({});
const action = hasCustomReject ? () => store.handleCustomButton?.(button) : () => store.rejectAnnotation({});

const onReject = async (e: React.MouseEvent) => {
const selected = store.annotationStore?.selected;
Expand Down
6 changes: 3 additions & 3 deletions web/libs/editor/src/components/BottomBar/buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { inject, observer } from "mobx-react";
import type React from "react";
import { memo, type ReactNode } from "react";
import { memo, type ReactElement } from "react";
import { Button } from "../../common/Button/Button";
import { Tooltip } from "../../common/Tooltip/Tooltip";

Expand All @@ -15,15 +15,15 @@ type MixedInParams = {
history: any;
};

export function controlsInjector<T extends {}>(fn: (props: T & MixedInParams) => ReactNode) {
export function controlsInjector<T extends {}>(fn: (props: T & MixedInParams) => ReactElement) {
const wrapped = inject(({ store }) => {
return {
store,
history: store?.annotationStore?.selected?.history,
};
})(fn);
// inject type doesn't handle the injected props, so we have to force cast it
return wrapped as unknown as (props: T) => ReactNode;
return wrapped as unknown as (props: T) => ReactElement;
Gondragos marked this conversation as resolved.
Show resolved Hide resolved
}

const TOOLTIP_DELAY = 0.8;
Expand Down
12 changes: 7 additions & 5 deletions web/libs/editor/src/components/TopBar/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IconCopy, IconInfo, IconViewAll, LsSettings, LsTrash } from "../../asse
import { Button } from "../../common/Button/Button";
import { Tooltip } from "../../common/Tooltip/Tooltip";
import { Elem } from "../../utils/bem";
import { FF_BULK_ANNOTATION, isFF } from "../../utils/feature-flags";
import { GroundTruth } from "../CurrentEntity/GroundTruth";
import { EditingHistory } from "./HistoryActions";
import { confirm } from "../../common/Modal/Modal";
Expand All @@ -13,14 +14,15 @@ export const Actions = ({ store }) => {
const saved = !entity.userGenerate || entity.sentUserGenerate;
const isPrediction = entity?.type === "prediction";
const isViewAll = annotationStore.viewingAll;
const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");

const onToggleVisibility = useCallback(() => {
annotationStore.toggleViewingAllAnnotations();
}, [annotationStore]);

return (
<Elem name="section">
{store.hasInterface("annotations:view-all") && (
{store.hasInterface("annotations:view-all") && !isBulkMode && (
<Tooltip title="View all annotations">
<Button
icon={<IconViewAll />}
Expand All @@ -37,11 +39,11 @@ export const Actions = ({ store }) => {
</Tooltip>
)}

{!isViewAll && store.hasInterface("ground-truth") && <GroundTruth entity={entity} />}
{!isViewAll && !isBulkMode && store.hasInterface("ground-truth") && <GroundTruth entity={entity} />}

{!isPrediction && !isViewAll && store.hasInterface("edit-history") && <EditingHistory entity={entity} />}

{!isViewAll && store.hasInterface("annotations:delete") && (
{!isViewAll && !isBulkMode && store.hasInterface("annotations:delete") && (
<Tooltip title="Delete annotation">
<Button
icon={<LsTrash />}
Expand All @@ -66,7 +68,7 @@ export const Actions = ({ store }) => {
</Tooltip>
)}

{!isViewAll && store.hasInterface("annotations:add-new") && saved && (
{!isViewAll && !isBulkMode && store.hasInterface("annotations:add-new") && saved && (
<Tooltip title={`Create copy of current ${entity.type}`}>
<Button
icon={<IconCopy style={{ width: 36, height: 36 }} />}
Expand Down Expand Up @@ -106,7 +108,7 @@ export const Actions = ({ store }) => {
}}
/>

{store.description && store.hasInterface("instruction") && (
{store.description && store.hasInterface("instruction") && !isBulkMode && (
<Button
icon={<IconInfo style={{ width: 16, height: 16 }} />}
primary={store.showingDescription}
Expand Down
11 changes: 7 additions & 4 deletions web/libs/editor/src/components/TopBar/TopBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IconViewAll, LsPlus } from "../../assets/icons";
import { Button } from "../../common/Button/Button";
import { Tooltip } from "../../common/Tooltip/Tooltip";
import { Block, Elem } from "../../utils/bem";
import { FF_DEV_3873, isFF } from "../../utils/feature-flags";
import { FF_BULK_ANNOTATION, FF_DEV_3873, isFF } from "../../utils/feature-flags";
import { AnnotationsCarousel } from "../AnnotationsCarousel/AnnotationsCarousel";
import { DynamicPreannotationsToggle } from "../AnnotationTab/DynamicPreannotationsToggle";
import { Actions } from "./Actions";
Expand All @@ -20,6 +20,9 @@ export const TopBar = observer(({ store }) => {
const isPrediction = entity?.type === "prediction";

const isViewAll = annotationStore?.viewingAll === true;
const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");

if (isFF(FF_DEV_3873) && isBulkMode) return null;

return store ? (
<Block name="topbar" mod={{ newLabelingUI: isFF(FF_DEV_3873) }}>
Expand Down Expand Up @@ -50,7 +53,7 @@ export const TopBar = observer(({ store }) => {
icon={<LsPlus />}
className={"topbar__button"}
type="text"
aria-label="View All"
aria-label="Create an annotation"
onClick={(event) => {
event.preventDefault();
const created = store.annotationStore.createAnnotation();
Expand All @@ -77,8 +80,8 @@ export const TopBar = observer(({ store }) => {
) : (
<>
<Elem name="group">
<CurrentTask store={store} />
{!isViewAll && (
{!isBulkMode && <CurrentTask store={store} />}
{!isViewAll && !isBulkMode && (
Gondragos marked this conversation as resolved.
Show resolved Hide resolved
<Annotations store={store} annotationStore={store.annotationStore} commentStore={store.commentStore} />
)}
<Actions store={store} />
Expand Down
8 changes: 7 additions & 1 deletion web/libs/editor/src/core/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { IAnyComplexType, IAnyStateTreeNode } from "mobx-state-tree/dist/in

import Registry from "./Registry";
import { parseValue } from "../utils/data";
import { FF_DEV_3391, isFF } from "../utils/feature-flags";
import { FF_BULK_ANNOTATION, FF_DEV_3391, isFF } from "../utils/feature-flags";
import { guidGenerator } from "../utils/unique";

interface ConfigNodeBaseProps {
Expand Down Expand Up @@ -233,6 +233,12 @@ function renderItem(ref: IAnyStateTreeNode, annotation: IAnnotation, includeKey
const typeName = type.name;
const View = Registry.getViewByModel(typeName);

const isBulkMode = isFF(FF_BULK_ANNOTATION) && annotation?.store?.hasInterface("annotation:bulk");
const isNotIndependentTag = el.isIndependent !== true;
if (isBulkMode && isNotIndependentTag) {
return null;
}

if (!View) {
throw new Error(`No view for model: ${typeName}`);
}
Expand Down
7 changes: 4 additions & 3 deletions web/libs/editor/src/stores/AppStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ export default types

function handleCustomButton(button) {
if (self.isSubmitting) return;
const buttonName = button.name;

handleSubmittingFlag(async () => {
const entity = self.annotationStore.selected;
Expand All @@ -717,7 +718,7 @@ export default types

const isDirty = entity.history.canUndo;

await getEnv(self).events.invoke("customButton", self, button, { isDirty, entity });
await getEnv(self).events.invoke("customButton", self, buttonName, { isDirty, entity, button });
self.incrementQueuePosition();
entity.dropDraft();
}, `Error during handling ${button} button, try again`);
Expand Down Expand Up @@ -905,7 +906,7 @@ export default types
self.annotationStore.selected.setSuggestions(dataParser(response));
self.setFlags({ awaitingSuggestions: false });
}
} catch (e) {
} catch (_e) {
self.setFlags({ awaitingSuggestions: false });
// @todo handle errors + situation when task is changed
}
Expand Down Expand Up @@ -939,7 +940,7 @@ export default types
}
}

function prevTask(e, shouldGoBack = false) {
function prevTask(_e, shouldGoBack = false) {
const length = shouldGoBack
? self.taskHistory.length - 1
: self.taskHistory.findIndex((x) => x.taskId === self.task.id) - 1;
Expand Down
32 changes: 20 additions & 12 deletions web/libs/editor/src/stores/CustomButton.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { types } from "mobx-state-tree";
import { applySnapshot, getSnapshot, type Instance, type SnapshotIn, types } from "mobx-state-tree";
import { guidGenerator } from "../utils/unique";

export type CustomButtonType = Instance<typeof CustomButton>;
export type CustomButtonSnType = SnapshotIn<typeof CustomButton>;
/**
* Custom buttons that can be injected from outside application.
* The only required property is `name`. If the `name` is one of the predefined buttons, it will be rendered as such.
* @see CustomControl in BottomBar/Controls
*/
export const CustomButton = types.model("CustomButton", {
id: types.optional(types.identifier, guidGenerator),
name: types.string,
title: types.string,
look: types.maybe(
types.enumeration(["primary", "danger", "destructive", "alt", "outlined", "active", "disabled"] as const),
),
tooltip: types.maybe(types.string),
ariaLabel: types.maybe(types.string),
disabled: types.maybe(types.boolean),
});
export const CustomButton = types
.model("CustomButton", {
id: types.optional(types.identifier, guidGenerator),
name: types.string,
title: types.string,
look: types.maybe(
types.enumeration(["primary", "danger", "destructive", "alt", "outlined", "active", "disabled"] as const),
),
tooltip: types.maybe(types.string),
ariaLabel: types.maybe(types.string),
disabled: types.maybe(types.boolean),
})
.actions((self) => ({
updateState(newState: CustomButtonSnType) {
applySnapshot(self, Object.assign({}, getSnapshot(self), newState));
},
}));
4 changes: 2 additions & 2 deletions web/libs/editor/src/stores/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ type MSTCommentStore = {
};

type MSTStore = {
customButtons: CustomControlProps.button[];
customButtons: CustomButtonsField;
settings: Record<string, boolean>;
isSubmitting: boolean;
// @todo WHAT IS THIS?
Expand All @@ -166,7 +166,7 @@ type MSTStore = {
commentStore: MSTCommentStore;

hasInterface: (name: string) => boolean;
handleCustomButton?: (name: string) => void;
handleCustomButton?: (button: CustomButtonType) => void;
submitAnnotation: (options?: any) => void;
updateAnnotation: (options?: any) => void;
rejectAnnotation: (options?: any) => void;
Expand Down
4 changes: 4 additions & 0 deletions web/libs/editor/src/tags/control/Choice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ const Model = types
isReadOnly() {
return self.readonly || self.parent?.isReadOnly();
},
// Indicates that it could exist without information about objects, taskData and regions
get isIndependent() {
return true;
},
}))
.volatile(() => ({
// `selected` is a predefined parameter, we cannot use it for state, so use `sel`
Expand Down
5 changes: 5 additions & 0 deletions web/libs/editor/src/tags/control/ClassificationBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ const ClassificationBase = types
getRegionElement() {
return self.elementRef.current;
},

// Indicates that it could exist without information about objects, taskData and regions
get isIndependent() {
return self.isClassificationTag && !self.perregion && !self.peritem && !self.value;
},
};
})
.actions((self) => {
Expand Down
Loading
Loading