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

Fixes brick position reset and simplifies form reinitialization #9351

Merged
merged 41 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3a208cd
#9323: consolidate editor slice reducers
twschiller Oct 22, 2024
25e4719
Fix missing session change action
twschiller Oct 22, 2024
d52781a
Fix selectionSeq
twschiller Oct 22, 2024
f97e8a3
Use better selector
twschiller Oct 22, 2024
7b33ff4
[WIP] in progress cleanup
twschiller Oct 22, 2024
f84e91c
[WIP] use activate mod where possible
twschiller Oct 23, 2024
5475120
Fix dead code
twschiller Oct 23, 2024
9530a8c
Common save method
twschiller Oct 23, 2024
ec247ac
Improve error handling
twschiller Oct 23, 2024
0957e74
Fix test fixtures
twschiller Oct 23, 2024
a80ab97
Add pre-condition
twschiller Oct 23, 2024
251b6d6
Set mod to replace on useSaveMod
twschiller Oct 23, 2024
f1f7812
Improve error message
twschiller Oct 23, 2024
05f725e
Hide modal
twschiller Oct 23, 2024
70ad346
Simplify passing around mod components
twschiller Oct 23, 2024
255dd84
Fix broken tests
twschiller Oct 23, 2024
651ad4c
Add code comment
twschiller Oct 23, 2024
88923e9
Fix comment reference
twschiller Oct 23, 2024
213ff26
Merge with main
twschiller Oct 23, 2024
0aa7458
Fix unused export
twschiller Oct 23, 2024
7cf322f
Fix test snapshot
twschiller Oct 23, 2024
5b1de6a
Fix snapshot
twschiller Oct 23, 2024
80e82fc
Addres PR comment
twschiller Oct 23, 2024
91cfb5b
Update snapshots
twschiller Oct 23, 2024
ec09a3c
PR comments
twschiller Oct 23, 2024
67fb4db
Split editor slice selectors
twschiller Oct 23, 2024
ce153b1
Merge with main
twschiller Oct 23, 2024
6776dcf
Fix stale snapshot
twschiller Oct 23, 2024
e5fd675
Fix dead code
twschiller Oct 23, 2024
6a2e9ce
Update src/pageEditor/store/editor/editorSelectors/editorAnalysisSele…
grahamlangford Oct 24, 2024
dbe66be
fix closures
grahamlangford Oct 24, 2024
383e0f5
Merge branch 'main' into feature/split-editor-selectors-2
grahamlangford Oct 24, 2024
226123c
fixes brick position reset; simplifies form reinitialization
grahamlangford Oct 24, 2024
3a39592
Merge branch 'main' into brick-outline-position-reinitialize
grahamlangford Oct 24, 2024
00f703f
prevents unnecessary resets in useRecoverFormStateIntegrityError
grahamlangford Oct 24, 2024
8aec8a9
pr fixes
grahamlangford Oct 24, 2024
2a39b66
type fix
grahamlangford Oct 24, 2024
17ad8e1
update snapshots
grahamlangford Oct 24, 2024
06817f0
remove extraneous console log
grahamlangford Oct 24, 2024
c4f0385
performance improvement
grahamlangford Oct 24, 2024
2fcfb70
add comments explaining useRecoverFormStateIntegrityError
grahamlangford Oct 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Index: brick-added
===================================================================
--- brick-added
+++ brick-added
@@ -6,78 +6,85 @@
@@ -6,78 +6,84 @@
containerSelector: span:has(> span:contains('Review in codespace'))
isAvailable:
allFrames: true
Expand Down Expand Up @@ -47,7 +47,6 @@ Index: brick-added
+ key: !nunjucks ''
+ id: '@pixies/util/set-mod-variable'
+ outputKey: output
+ root: null
+ rootMode: document
+ - config:
value: !nunjucks ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Index: brick-removed
===================================================================
--- brick-removed
+++ brick-removed
@@ -6,85 +6,78 @@
@@ -6,84 +6,78 @@
containerSelector: span:has(> span:contains('Review in codespace'))
isAvailable:
allFrames: true
Expand Down Expand Up @@ -47,7 +47,6 @@ Index: brick-removed
- key: !nunjucks ''
- id: '@pixies/util/set-mod-variable'
- outputKey: output
- root: null
- rootMode: document
- - config:
value: !nunjucks ''
Expand Down
20 changes: 9 additions & 11 deletions src/pageEditor/panes/ModComponentEditorPane.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import React from "react";
import { render, screen, within } from "@/pageEditor/testHelpers";
import { render, screen, within, userEvent } from "@/pageEditor/testHelpers";
import ModComponentEditorPane from "./ModComponentEditorPane";
import { actions as editorActions } from "@/pageEditor/store/editor/editorSlice";
import { selectActiveModComponentFormState } from "@/pageEditor/store/editor/editorSelectors";
Expand All @@ -26,7 +26,6 @@ import { echoBrick, teapotBrick } from "@/runtime/pipelineTests/testHelpers";
import { defaultBrickConfig } from "@/bricks/util";
import { waitForEffect } from "@/testUtils/testHelpers";
import registerDefaultWidgets from "@/components/fields/schemaFields/widgets/registerDefaultWidgets";
import userEvent from "@testing-library/user-event";
import { JQTransformer } from "@/bricks/transformers/jq";
import { AlertEffect } from "@/bricks/effects/alert";
import ForEach from "@/bricks/transformers/controlFlow/ForEach";
Expand Down Expand Up @@ -430,6 +429,7 @@ async function renderEditorPaneWithBasicFormState() {
const modComponentFormState = getFormStateWithSubPipelines();
const activeNodeId =
modComponentFormState.modComponent.brickPipeline[0]!.instanceId;

const utils = render(
<div>
<ModComponentEditorPane />
Expand Down Expand Up @@ -473,7 +473,7 @@ describe("can remove a node", () => {
await renderEditorPaneWithBasicFormState();

// Nodes are: Foundation, Echo, ForEach: [Echo]
// Select the second Echo block
// Select the second Echo brick
await immediateUserEvent.click(screen.getAllByText(/echo brick/i)[1]!);

// Click the remove button
Expand Down Expand Up @@ -655,6 +655,7 @@ describe("can copy and paste a node", () => {
// There should be 5 paste buttons
const pasteButtons = screen.getAllByTestId(/-paste-brick/i);
expect(pasteButtons).toHaveLength(5);

// Click the last one
await immediateUserEvent.click(pasteButtons[4]!);

Expand All @@ -671,7 +672,6 @@ describe("can copy and paste a node", () => {

describe("validation", () => {
function expectEditorError(container: HTMLElement, errorMessage: string) {
// eslint-disable-next-line testing-library/no-node-access -- TODO: use a better selector
const errorBadge = container.querySelector(
'.active[data-testid="editor-node"] span.badge',
);
Expand Down Expand Up @@ -764,7 +764,7 @@ describe("validation", () => {

// Ensure 2 nodes have error badges
expect(
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- TODO: use a better selector
// eslint-disable-next-line testing-library/no-container -- TODO: use a better selector
container.querySelectorAll('[data-testid="editor-node"] span.badge'),
).toHaveLength(2);

Expand All @@ -773,7 +773,7 @@ describe("validation", () => {
store.dispatch(editorActions.setActiveModComponentId(modComponent2.uuid));

// Ensure no error is displayed
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- TODO: use a better selector
// eslint-disable-next-line testing-library/no-container -- TODO: use a better selector
const errorBadgesOfAnotherModComponent = container.querySelectorAll(
'[data-testid="editor-node"] span.badge',
);
Expand All @@ -787,7 +787,7 @@ describe("validation", () => {

// Should show 2 error in the Node Layout
expect(
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- TODO: use a better selector
// eslint-disable-next-line testing-library/no-container -- TODO: use a better selector
container.querySelectorAll('[data-testid="editor-node"] span.badge'),
).toHaveLength(2);

Expand Down Expand Up @@ -992,9 +992,8 @@ describe("brick validation in Add Brick Modal UI", () => {
await tickAsyncEffects();

// Check for the alert on hover
const firstResult =
// eslint-disable-next-line testing-library/no-node-access -- TODO: use a better selector
screen.getAllByRole("button", { name: /add/i })[0]!.parentElement!;
const firstResult = screen.getAllByRole("button", { name: /add/i })[0]!
.parentElement!;
await immediateUserEvent.hover(firstResult);
expect(firstResult).toHaveTextContent("is not allowed in this pipeline");
},
Expand Down Expand Up @@ -1037,7 +1036,6 @@ describe("brick validation in Add Brick Modal UI", () => {

// Assert that no UiPath bricks are available
for (const button of addButtons) {
// eslint-disable-next-line testing-library/no-node-access -- TODO: use a better selector
const brick = button.parentElement;
expect(brick).not.toHaveTextContent("uipath");
}
Expand Down
40 changes: 30 additions & 10 deletions src/pageEditor/panes/ModComponentEditorPane.tsx
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional changes are all here

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useEffect } from "react";
import React, { useEffect, useMemo, useRef } from "react";
import {
actions,
actions as editorActions,
Expand All @@ -36,6 +36,7 @@ import {
import IntegrationsSliceModIntegrationsContextAdapter from "@/integrations/store/IntegrationsSliceModIntegrationsContextAdapter";
import { assertNotNullish } from "@/utils/nullishUtils";
import useRegisterDraftModInstanceOnAllFrames from "@/pageEditor/hooks/useRegisterDraftModInstanceOnAllFrames";
import { usePreviousValue } from "@/hooks/usePreviousValue";

// CHANGE_DETECT_DELAY_MILLIS should be low enough so that sidebar gets updated in a reasonable amount of time, but
// high enough that there isn't an entry lag in the page editor
Expand Down Expand Up @@ -87,26 +88,45 @@ const EditorPaneContent: React.VoidFunctionComponent<{
);
};

const ModComponentEditorPane: React.VFC = () => {
// Inject the draft mod instance into the page while editing
useRegisterDraftModInstanceOnAllFrames();

/**
* Returns the active mod component form state. Responds to updates in the editor state for use with triggering rerenders.
*/
function useInitialValues(): ModComponentFormState {
grahamlangford marked this conversation as resolved.
Show resolved Hide resolved
const editorUpdateKey = useSelector(selectEditorUpdateKey);
const activeModComponentFormState = useSelector(
selectActiveModComponentFormState,
);

assertNotNullish(
activeModComponentFormState,
"ModComponentEditorPane requires activeModComponentFormState",
);
const editorUpdateKey = useSelector(selectEditorUpdateKey);
// Key to force reload of component when user selects a different mod component from the sidebar

// Key to force reinitialization of formik when user selects a different mod component from the sidebar
const key = `${activeModComponentFormState.uuid}-${activeModComponentFormState.installed}-${editorUpdateKey}`;
const prevKey = usePreviousValue(key);
const activeModComponentFormStateRef = useRef(activeModComponentFormState);

return useMemo(() => {
if (key === prevKey) {
return activeModComponentFormStateRef.current;
}

activeModComponentFormStateRef.current = activeModComponentFormState;
return activeModComponentFormState;
}, [key, prevKey, activeModComponentFormState]);
}

const ModComponentEditorPane: React.VFC = () => {
// Inject the draft mod instance into the page while editing
useRegisterDraftModInstanceOnAllFrames();
const initialValues = useInitialValues();

return (
<ErrorBoundary key={key}>
<ErrorBoundary>
<Formik
key={key}
initialValues={activeModComponentFormState}
enableReinitialize
initialValues={initialValues}
onSubmit={() => {
console.error(
"Formik's submit should not be called to save a mod component.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ export const selectActiveNodeId = createSelector(
export const selectActiveNodeInfo = createSelector(
selectActiveBrickPipelineUIState,
selectActiveNodeId,
(
uiState: Nullishable<BrickPipelineUIState>,
activeNodeId: Nullishable<UUID>,
) => {
(uiState, activeNodeId) => {
assertNotNullish(
uiState,
`uiState is ${typeof uiState === "object" ? "null" : "undefined"}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,15 @@ const usePipelineNodes = (): {
}));
};

const showAddBlock = isApiAtLeastV2 && (index < lastIndex || showAppend);
const showAddBrick = isApiAtLeastV2 && (index < lastIndex || showAppend);
const showBiggerActions = index === lastIndex && isRootPipeline;
const showAddMessage = showAddBlock && showBiggerActions;
const showAddMessage = showAddBrick && showBiggerActions;

const brickNodeActions: NodeAction[] = [];
const nodeId = instanceId;

// TODO: Refactoring - remove code duplication in the node actions here
if (showAddBlock) {
if (showAddBrick) {
brickNodeActions.push({
name: `${nodeId}-add-brick`,
icon: faPlusCircle,
Expand Down Expand Up @@ -415,7 +415,7 @@ const usePipelineNodes = (): {
};

if (block) {
assertNotNullish(nodeId, "nodeId is required to get block annotations");
assertNotNullish(nodeId, "nodeId is required to get brick annotations");
// Handle race condition on pipelineMap updates
// eslint-disable-next-line security/detect-object-injection -- relying on nodeId being a UUID
const blockPath = maybePipelineMap?.[nodeId]?.path;
Expand Down
11 changes: 9 additions & 2 deletions src/pageEditor/tabs/effect/BrickConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ import { useDispatch } from "react-redux";
import { actions as editorActions } from "@/pageEditor/store/editor/editorSlice";

/**
* Handles possible NPE from `config.value` being undefined.
* Handles possible NPE from the brick configuration not being migrated.
* Ensures that the brickPipelineUIState is valid
*
* This originally happened during the extension -> modComponent form state migration.
* See https://github.com/pixiebrix/pixiebrix-extension/issues/8781
*/
Expand All @@ -60,7 +62,12 @@ function useRecoverFormStateIntegrityError(name: string) {
const [config] = useField<BrickConfig | undefined>(name);
const dispatch = useDispatch();

if (!config.value) {
/**
* If the name doesn't include "modComponent", the brickPipelineUIstate has not been migrated
* and is invalid. Previously, we checked for the existence of config.value, but config.value can be undefined
* (see usage in BrickConfiguration). Remounting the form instead of reinitializing hid that logic error.
*/
if (!name.includes("modComponent")) {
Copy link
Collaborator

@mnholtz mnholtz Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging: documentation for this func needs to be updated if we're removing the check for config.value explicitly.

Non-blocking as I trust you on form state migration: but can you help me understand how this is equivalent?

Copy link
Contributor

@twschiller twschiller Oct 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree see outdated comment: #9351 (comment)

I have very limited understanding of the context behind that hook. I did rename it recently though to be more clear on why it exists (it used to just be called useReset (or a similar name)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improved the comment. Let me know if there's still confusion

dispatch(
editorActions.setModComponentFormState({
modComponentFormState: context.values,
Expand Down
Loading