Skip to content

Commit

Permalink
feat: LEAP-1173: Disable the postpone option if the skip interface is…
Browse files Browse the repository at this point in the history
…n't set (#6048)

### PR fulfills these requirements
- [x] Commit message(s) and PR title follows the format
`[fix|feat|ci|chore|doc]: TICKET-ID: Short description of change made`
ex. `fix: DEV-XXXX: Removed inconsistent code usage causing intermittent
errors`
- [x] Tests for the changes have been added/updated (for bug
fixes/features)
- [ ] Docs have been added/updated (for bug fixes/features)
- [x] Best efforts were made to ensure docs/code are concise and
coherent (checked for spelling/grammatical errors, commented out code,
debug logs etc.)
- [x] Self-reviewed and ran all changes on a local instance (for bug
fixes/features)


#### Change has impacts in these area(s)
_(check all that apply)_
- [ ] Product design
- [ ] Backend (Database)
- [ ] Backend (API)
- [x] Frontend


**_Reason for the Change_**
This change is necessary to enforce task flow integrity by preventing
users from bypassing the task sequence. It ensures that tasks intended
to be completed in order cannot be skipped, thus maintaining the
workflow's intended progression.

**_What Does This Fix?_**
When skip interface is not set, postpone option will be disabled


### What level of testing was included in the change?
_(check all that apply)_
- [ ] e2e
- [ ] integration
- [x] unit
  • Loading branch information
juliosgarbi committed Jul 3, 2024
1 parent a5fc063 commit 470b83b
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 3 deletions.
5 changes: 4 additions & 1 deletion web/libs/editor/src/components/BottomBar/CurrentTask.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button } from "../../common/Button/Button";
import { Block, Elem } from "../../utils/bem";
import { guidGenerator } from "../../utils/unique";
import { isDefined } from "../../utils/utilities";
import { FF_TASK_COUNT_FIX, isFF } from "../../common/Tooltip/Tooltip";
import { FF_LEAP_1173, FF_TASK_COUNT_FIX, isFF } from "../../utils/feature-flags";
import "./CurrentTask.styl";

export const CurrentTask = observer(({ store }) => {
Expand All @@ -13,10 +13,12 @@ export const CurrentTask = observer(({ store }) => {
}, [store.taskHistory]);

const historyEnabled = store.hasInterface("topbar:prevnext");

// @todo some interface?
const canPostpone =
!isDefined(store.annotationStore.selected.pk) &&
!store.canGoNextTask &&
(!isFF(FF_LEAP_1173) || store.hasInterface("skip")) &&
!store.hasInterface("review") &&
store.hasInterface("postpone");

Expand Down Expand Up @@ -50,6 +52,7 @@ export const CurrentTask = observer(({ store }) => {
<Elem
tag={Button}
name="prevnext"
data-testid="next-task"
mod={{
next: true,
disabled: !store.canGoNextTask && !canPostpone,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { render } from "@testing-library/react";
import { CurrentTask } from "../CurrentTask";
import { BlockContext, cn } from "../../../utils/bem.ts";
import { FF_LEAP_1173 } from "../../../utils/feature-flags";
import { mockFF } from "../../../../__mocks__/global";

const ff = mockFF();

describe("CurrentTask", () => {
let store: any;

beforeAll(() => {
ff.setup();
ff.set({
[FF_LEAP_1173]: true,
});
});

beforeEach(() => {
// Initialize your store with default values
store = {
annotationStore: { selected: { pk: null } },
canGoNextTask: false,
canGoPrevTask: false,
hasInterface: jest.fn(),
taskHistory: [
{
taskId: 6627,
annotationId: null,
},
{
taskId: 6616,
annotationId: null,
},
],
task: { id: 6616 },
commentStore: {
loading: "list",
comments: [],
setAddedCommentThisSession: jest.fn(),
},
queuePosition: 1,
prevTask: jest.fn(),
nextTask: jest.fn(),
postponeTask: jest.fn(),
queueTotal: 22,
};
});

it("sets canPostpone correctly", () => {
// check if next-task is enabled
store.hasInterface.mockImplementation((interfaceName: string) =>
["skip", "postpone", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
);

const { rerender, getByTestId } = render(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(false);

// check if next-task is disabled removing the postpone interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["skip", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);

// check if next-task is disabled removing the skip interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["postpone", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);

// check if next-task is disabled removing both skip and postpone interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);

// check if next-task is disabled setting review interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["review", "skip", "postpone", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);
});
});
4 changes: 3 additions & 1 deletion web/libs/editor/src/components/TopBar/CurrentTask.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { useEffect, useState } from "react";
import { Button } from "../../common/Button/Button";
import { Block, Elem } from "../../utils/bem";
import { FF_DEV_3873, FF_DEV_4174, FF_TASK_COUNT_FIX, isFF } from "../../utils/feature-flags";
import { FF_DEV_3873, FF_DEV_4174, FF_LEAP_1173, FF_TASK_COUNT_FIX, isFF } from "../../utils/feature-flags";
import { guidGenerator } from "../../utils/unique";
import { isDefined } from "../../utils/utilities";
import "./CurrentTask.styl";
Expand Down Expand Up @@ -44,6 +44,7 @@ export const CurrentTask = observer(({ store }) => {
// @todo some interface?
let canPostpone =
!isDefined(store.annotationStore.selected.pk) &&
(!isFF(FF_LEAP_1173) || store.hasInterface("skip")) &&
!store.canGoNextTask &&
!store.hasInterface("review") &&
store.hasInterface("postpone");
Expand Down Expand Up @@ -90,6 +91,7 @@ export const CurrentTask = observer(({ store }) => {
<Elem
tag={Button}
name="prevnext"
data-testid="next-task"
mod={{
next: true,
disabled: !store.canGoNextTask && !canPostpone,
Expand Down
136 changes: 136 additions & 0 deletions web/libs/editor/src/components/TopBar/__tests__/CurrentTask.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { render } from "@testing-library/react";
import { CurrentTask } from "../CurrentTask";
import { BlockContext, cn } from "../../../utils/bem.ts";
import { FF_LEAP_1173 } from "../../../utils/feature-flags";
import { mockFF } from "../../../../__mocks__/global";

const ff = mockFF();

describe("CurrentTask", () => {
let store: any;

beforeAll(() => {
ff.setup();
ff.set({
[FF_LEAP_1173]: true,
});
});

beforeEach(() => {
// Initialize your store with default values
store = {
annotationStore: { selected: { pk: null } },
canGoNextTask: false,
canGoPrevTask: false,
hasInterface: jest.fn(),
taskHistory: [
{
taskId: 6627,
annotationId: null,
},
{
taskId: 6616,
annotationId: null,
},
],
task: { id: 6616 },
commentStore: {
loading: "list",
comments: [],
setAddedCommentThisSession: jest.fn(),
},
queuePosition: 1,
prevTask: jest.fn(),
nextTask: jest.fn(),
postponeTask: jest.fn(),
queueTotal: 22,
};
});

it("sets canPostpone correctly", () => {
// check if next-task is enabled
store.hasInterface.mockImplementation((interfaceName: string) =>
["skip", "postpone", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
);

const { rerender, getByTestId } = render(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(false);

// check if next-task is disabled removing the postpone interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["skip", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);

// check if next-task is disabled removing the skip interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["postpone", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);

// check if next-task is disabled removing both skip and postpone interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);

// check if next-task is disabled setting review interface
store = {
...store,
hasInterface: jest
.fn()
.mockImplementation((interfaceName: string) =>
["review", "skip", "postpone", "topbar:prevnext", "topbar:task-counter"].includes(interfaceName),
),
};

rerender(
<BlockContext.Provider value={cn("block-name")}>
<CurrentTask store={store} />
</BlockContext.Provider>,
);

expect(getByTestId("next-task").disabled).toBe(true);
});
});
2 changes: 1 addition & 1 deletion web/libs/editor/src/utils/bem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const assembleClass = (block: string, elem?: string, mix?: CNMix | CNMix[], mod?
return finalClass.map(attachNamespace).join(" ");
};

const BlockContext = createContext<CN | null>(null);
export const BlockContext = createContext<CN | null>(null);

export const cn = (block: string, options: CNOptions = {}): CN => {
const { elem, mix, mod } = options ?? {};
Expand Down
6 changes: 6 additions & 0 deletions web/libs/editor/src/utils/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,12 @@ export const FF_REVIEWER_FLOW = "fflag_feat_all_leap_1081_reviewer_flow_updates"

export const FF_CUSTOM_SCRIPT = "fflag_feat_all_leap_883_custom_script_270524_short";

/**
* Disable the postpone option if the skip interface isn't set
* @link https://app.launchdarkly.com/projects/default/flags/fflag_feat_front_leap_1173_disable_postpone_skip_short
*/
export const FF_LEAP_1173 = "fflag_feat_front_leap_1173_disable_postpone_skip_short";

Object.assign(window, {
APP_SETTINGS: {
...(window.APP_SETTINGS ?? {}),
Expand Down

0 comments on commit 470b83b

Please sign in to comment.