Skip to content

Commit

Permalink
fix operator prompt hidden for executing, output, and error (#4445)
Browse files Browse the repository at this point in the history
* fix operator prompt hidden for executing, output, and error

* add e2e tests for operators prompt

* operator drawer prompt tweaks

* mv to asserter

* lint

---------

Co-authored-by: Benjamin Kane <[email protected]>
  • Loading branch information
imanjra and benjaminpkane authored Jun 4, 2024
1 parent 8471d1f commit d2ca5f7
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 30 deletions.
20 changes: 17 additions & 3 deletions app/packages/operators/src/OperatorPrompt/OperatorDrawerPrompt.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Resizable } from "@fiftyone/components";
import { Resizable, scrollable } from "@fiftyone/components";
import { Close } from "@mui/icons-material";
import { Box, IconButton, Stack } from "@mui/material";
import { useState } from "react";
Expand Down Expand Up @@ -32,6 +32,7 @@ export default function OperatorDrawerPrompt(props: OperatorPromptPropsType) {
onResizeReset={() => {
setWidth(DEFAULT_WIDTH);
}}
data-cy="operators-prompt-drawer"
>
<IconButton
onClick={prompt.close}
Expand All @@ -42,8 +43,21 @@ export default function OperatorDrawerPrompt(props: OperatorPromptPropsType) {
<Box sx={{ p: 1 }}>
<OperatorPromptHeader title={title} />
</Box>
<OperatorPromptBody operatorPrompt={prompt} />
<Stack direction="row" spacing={1} justifyContent="center">
<Box
data-cy="operators-prompt-drawer-content"
sx={{ overflow: "auto" }}
className={scrollable}
>
<OperatorPromptBody operatorPrompt={prompt} />
</Box>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
data-cy="operators-prompt-drawer-footer"
sx={{ py: 1 }}
>
<OperatorPromptFooter {...otherConfigs} />
</Stack>
</Resizable>
Expand Down
22 changes: 3 additions & 19 deletions app/packages/operators/src/OperatorPrompt/OperatorModalPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,12 @@ import { getOperatorPromptConfigs } from "../utils";

export default function OperatorModalPrompt(props: OperatorPromptPropsType) {
const { prompt } = props;
const {
title,
hasValidationErrors,
resolving,
pendingResolve,
validationErrorsStr,
...otherConfigs
} = getOperatorPromptConfigs(prompt);
const promptConfig = getOperatorPromptConfigs(prompt);
return (
<OperatorPalette
{...otherConfigs}
title={title}
onClose={otherConfigs.onCancel || prompt.close}
{...promptConfig}
submitOnControlEnter
disableSubmit={hasValidationErrors || resolving || pendingResolve}
disabledReason={
hasValidationErrors
? "Cannot execute operator with validation errors\n\n" +
validationErrorsStr
: "Cannot execute operator while validating form"
}
loading={resolving || pendingResolve}
dialogProps={{ PaperProps: { "data-cy": "operators-prompt-modal" } }}
>
<PaletteContentContainer>
<OperatorPromptBody operatorPrompt={prompt} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default function OperatorViewModal() {
onSubmit={io.hide}
onClose={io.hide}
submitButtonText="Done"
dialogProps={{ PaperProps: { "data-cy": "operators-prompt-view-modal" } }}
>
<PaletteContentContainer>
<OperatorIO schema={io.schema} data={io.data || {}} type={io.type} />
Expand Down
2 changes: 0 additions & 2 deletions app/packages/operators/src/OperatorPrompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ function DynamicOperatorPrompt() {
const target = getPromptTarget(prompt);
const Component = getPromptComponent(prompt);

if (!prompt.resolvedIO.input && !prompt.resolvedIO.output) return null;

return createPortal(<Component prompt={prompt} />, target);
}

Expand Down
10 changes: 10 additions & 0 deletions app/packages/operators/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export function getOperatorPromptConfigs(operatorPrompt: OperatorPromptType) {
const validationErrorsStr = formatValidationErrors(
operatorPrompt.validationErrors
);
const loading = resolving || pendingResolve;
const disableSubmit = hasValidationErrors || resolving || pendingResolve;
const disabledReason = hasValidationErrors
? "Cannot execute operator with validation errors\n\n" + validationErrorsStr
: "Cannot execute operator while validating form";
const onClose = onCancel || operatorPrompt.close;

return {
title,
Expand All @@ -99,6 +105,10 @@ export function getOperatorPromptConfigs(operatorPrompt: OperatorPromptType) {
cancelButtonText,
onSubmit,
onCancel,
loading,
disableSubmit,
disabledReason,
onClose,
};
}

Expand Down
79 changes: 79 additions & 0 deletions e2e-pw/src/oss/poms/operators/operators-prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Locator, Page, expect } from "src/oss/fixtures";

export class OperatorsPromptPom {
readonly page: Page;
readonly locator: Locator;
readonly assert: OperatorsPromptAsserter;
readonly selectionCount: Locator;
readonly type: PromptType;

constructor(page: Page, type: PromptType = "modal") {
this.page = page;
this.assert = new OperatorsPromptAsserter(this);
this.locator = this.page.getByTestId(`operators-prompt-${type}`);
this.type = type;
}

get content() {
if (this.type === "drawer") {
return this.locator.getByTestId("operators-prompt-drawer-content");
}
return this.locator.locator(".MuiDialogContent-root").first();
}

get footer() {
if (this.type === "drawer") {
return this.locator.getByTestId("operators-prompt-drawer-footer");
}
return this.locator.locator(".MuiDialogActions-root").first();
}

get executeButton() {
return this.footer.locator('button:text("Execute")');
}

async execute() {
await this.assert.canExecute();
return this.executeButton.click();
}

cancel() {
return this.footer.locator('button:text("Cancel")').click();
}

close() {
return this.footer.locator('button:text("Close")').click();
}

done() {
return this.footer.locator('button:text("Done")').click();
}
}

class OperatorsPromptAsserter {
constructor(private readonly panelPom: OperatorsPromptPom) {}

async isOpen() {
await expect(this.panelPom.locator).toBeVisible();
}
async isClosed() {
await expect(this.panelPom.locator).not.toBeVisible();
}
async isExecuting() {
await expect(this.panelPom.locator).toContainText("Executing...");
}
async canExecute() {
await expect(this.panelPom.executeButton).toBeEnabled();
}

async isValidated() {
await expect(
this.panelPom.footer.locator(".MuiCircularProgress-root")
).toBeHidden();
await expect(
this.panelPom.footer.locator(".MuiCircularProgress-root")
).toBeHidden();
}
}

type PromptType = "modal" | "drawer" | "view-modal";
7 changes: 1 addition & 6 deletions e2e-pw/src/oss/specs/operators/built-in-operators.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { test as base } from "src/oss/fixtures";
import { OperatorsBrowserPom } from "src/oss/poms/operators/operators-browser";
import { HistogramPom } from "src/oss/poms/panels/histogram-panel";
import { ViewBarPom } from "src/oss/poms/viewbar/viewbar";
import { getUniqueDatasetNameWithPrefix } from "src/oss/utils";

const datasetName = getUniqueDatasetNameWithPrefix(`built-in-operators`);
const datasetName = getUniqueDatasetNameWithPrefix("built-in-operators");
const test = base.extend<{
operatorsBrowser: OperatorsBrowserPom;
viewBar: ViewBarPom;
histogramPanel: HistogramPom;
}>({
operatorsBrowser: async ({ page }, use) => {
await use(new OperatorsBrowserPom(page));
},
viewBar: async ({ page }, use) => {
await use(new ViewBarPom(page));
},
histogramPanel: async ({ page, eventUtils }, use) => {
await use(new HistogramPom(page, eventUtils));
},
});

test.beforeAll(async ({ fiftyoneLoader }) => {
Expand Down
138 changes: 138 additions & 0 deletions e2e-pw/src/oss/specs/operators/prompt.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { test as base, expect } from "src/oss/fixtures";
import { OperatorsBrowserPom } from "src/oss/poms/operators/operators-browser";
import { OperatorsPromptPom } from "src/oss/poms/operators/operators-prompt";
import { getUniqueDatasetNameWithPrefix } from "src/oss/utils";

const datasetName = getUniqueDatasetNameWithPrefix("operators-prompt");
const test = base.extend<{
operatorsBrowser: OperatorsBrowserPom;
operatorsPrompt: OperatorsPromptPom;
operatorsPromptViewModal: OperatorsPromptPom;
operatorsPromptDrawer: OperatorsPromptPom;
}>({
operatorsBrowser: async ({ page }, use) => {
await use(new OperatorsBrowserPom(page));
},
operatorsPrompt: async ({ page }, use) => {
await use(new OperatorsPromptPom(page));
},
operatorsPromptViewModal: async ({ page }, use) => {
await use(new OperatorsPromptPom(page, "view-modal"));
},
operatorsPromptDrawer: async ({ page }, use) => {
await use(new OperatorsPromptPom(page, "drawer"));
},
});

test.beforeAll(async ({ fiftyoneLoader }) => {
await fiftyoneLoader.executePythonCode(`
import fiftyone as fo
dataset = fo.Dataset("${datasetName}")
dataset.persistent = True
samples = []
for i in range(0, 10):
sample = fo.Sample(
filepath=f"{i}.png",
detections=fo.Detections(detections=[fo.Detection(label=f"label-{i}")]),
classification=fo.Classification(label=f"label-{i}"),
bool=i % 2 == 0,
str=f"{i}",
int=i % 2,
float=i / 2,
list_str=[f"{i}"],
list_int=[i % 2],
list_float=[i / 2],
list_bool=[i % 2 == 0],
)
samples.append(sample)
dataset.add_samples(samples)`);
});

test.beforeEach(async ({ page, fiftyoneLoader }) => {
await fiftyoneLoader.waitUntilGridVisible(page, datasetName);
});

test("Prompt: Cancel modal", async ({ operatorsBrowser, operatorsPrompt }) => {
await operatorsBrowser.show();
await operatorsBrowser.search("E2E");
await operatorsBrowser.choose("E2E: Say hello in modal");
await operatorsPrompt.locator.locator("input").first().fill("E2E");
await operatorsPrompt.assert.isOpen();
await operatorsPrompt.cancel();
await operatorsPrompt.assert.isClosed();
});

test("Prompt: Say hello in modal", async ({
operatorsBrowser,
operatorsPrompt,
}) => {
await operatorsBrowser.show();
await operatorsBrowser.search("E2E");
await operatorsBrowser.choose("E2E: Say hello in modal");
await operatorsPrompt.assert.isOpen();
await operatorsPrompt.locator
.locator("input")
.first()
.pressSequentially("E2E");
await operatorsPrompt.assert.isValidated();
await operatorsPrompt.execute();
await operatorsPrompt.assert.isExecuting();
await expect(operatorsPrompt.content).toContainText("Message:Hi E2E!");
await operatorsPrompt.close();
await operatorsPrompt.assert.isClosed();
});

test("Prompt: Cancel drawer", async ({
operatorsBrowser,
operatorsPromptDrawer,
}) => {
await operatorsBrowser.show();
await operatorsBrowser.search("E2E");
await operatorsBrowser.choose("E2E: Say hello in drawer");
await operatorsPromptDrawer.locator.locator("input").first().fill("E2E");
await operatorsPromptDrawer.assert.isOpen();
await operatorsPromptDrawer.cancel();
await operatorsPromptDrawer.assert.isClosed();
});

test("Prompt: Say hello in drawer", async ({
operatorsBrowser,
operatorsPromptDrawer,
}) => {
await operatorsBrowser.show();
await operatorsBrowser.search("E2E");
await operatorsBrowser.choose("E2E: Say hello in drawer");
await operatorsPromptDrawer.assert.isOpen();
await operatorsPromptDrawer.locator
.locator("input")
.first()
.pressSequentially("E2E");
await operatorsPromptDrawer.assert.isValidated();
await operatorsPromptDrawer.execute();
await operatorsPromptDrawer.assert.isExecuting();
await expect(operatorsPromptDrawer.content).toContainText("Message:Hi E2E!");
await operatorsPromptDrawer.close();
await operatorsPromptDrawer.assert.isClosed();
});

test("Prompt: Progress", async ({
operatorsBrowser,
operatorsPrompt,
operatorsPromptViewModal,
}) => {
await operatorsBrowser.show();
await operatorsBrowser.search("E2E");
await operatorsBrowser.choose("E2E: Progress");
await operatorsPrompt.assert.isExecuting();
await expect(operatorsPromptViewModal.content).toContainText(
"Loading 1 of 2"
);
await expect(operatorsPromptViewModal.content).toContainText(
"Loading 2 of 2"
);
await operatorsPromptViewModal.done();
await operatorsPrompt.assert.isClosed();
await operatorsPromptViewModal.assert.isClosed();
});
Loading

0 comments on commit d2ca5f7

Please sign in to comment.