Skip to content

Commit c7c1d87

Browse files
authored
fix(Designer): Fixed loop causing rerender in monitoring view (#6686)
* Fixed node rerender bug * Added E2E test coverage for loop issue * Removed s
1 parent d37b76f commit c7c1d87

File tree

5 files changed

+58
-3
lines changed

5 files changed

+58
-3
lines changed

e2e/designer/real-api/monitoring/NestedLoops/nestedLoops.spec.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
import { expect, test } from '../../../fixtures/real-api';
2+
3+
declare global {
4+
interface Window {
5+
__REDUX_ACTION_LOG__?: string[];
6+
}
7+
}
8+
29
test.describe(
310
'Nested loops',
411
{
@@ -16,7 +23,7 @@ test.describe(
1623
// Load run history
1724
await page.getByRole('menuitem', { name: 'Run History' }).click();
1825
await page.waitForTimeout(3000);
19-
await page.getByRole('gridcell', { name: '/18/2025, 3:25:03 PM' }).click();
26+
await page.getByRole('gridcell', { name: '/18/2025' }).click();
2027

2128
// Check for outermost foreach
2229
await expect(page.getByTestId('msla-pill-foreach_status')).toBeVisible();
@@ -47,6 +54,22 @@ test.describe(
4754

4855
// Check inner loop to be interactive
4956
await page.getByTestId('msla-pager-v2-foreach_2').getByLabel('Next').click();
57+
58+
// Clear action log
59+
await page.evaluate(() => (window.__REDUX_ACTION_LOG__ = []));
60+
await page.waitForTimeout(1000);
61+
62+
// Collapse / expand inner loop
63+
await page.getByLabel('Foreach-2 operation').click({ button: 'right' });
64+
await page.getByText('Collapse nested').click({ force: true });
65+
await page.getByLabel('Foreach-2 operation').click({ button: 'right' });
66+
await page.getByText('Expand nested').click({ force: true });
67+
await page.waitForTimeout(2000);
68+
69+
// Confirm the actions only triggered once
70+
const actionLog = (await page.evaluate(() => window.__REDUX_ACTION_LOG__)) ?? [];
71+
const numActions = actionLog?.filter((action) => action === 'workflow/setRepetitionRunData').length;
72+
expect(numActions).toEqual(1);
5073
});
5174
}
5275
);

libs/designer/src/lib/core/store.ts

+18
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import { configureStore } from '@reduxjs/toolkit';
1616
import type {} from 'redux-thunk';
1717
import { storeStateHistoryMiddleware } from './utils/middleware';
1818

19+
declare global {
20+
interface Window {
21+
__REDUX_ACTION_LOG__?: string[];
22+
}
23+
}
24+
1925
export const store = configureStore({
2026
reducer: {
2127
workflow: workflowReducer,
@@ -43,6 +49,18 @@ if (process.env.NODE_ENV === 'development') {
4349
(window as any).DesignerStore = store;
4450
}
4551

52+
// Log actions for testing purposes
53+
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
54+
window.__REDUX_ACTION_LOG__ = [];
55+
const originalDispatch = store.dispatch;
56+
store.dispatch = (action: any) => {
57+
window.__REDUX_ACTION_LOG__?.push(action.type);
58+
return originalDispatch(action);
59+
};
60+
}
61+
62+
export default store;
63+
4664
// Infer the `AppStore` from the store itself
4765
export type AppStore = typeof store;
4866
// Infer the `RootState` and `AppDispatch` types from the store itself

libs/designer/src/lib/ui/CustomNodes/OperationCardNode.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const DefaultNode = ({ targetPosition = Position.Top, sourcePosition = Position.
7676
const runData = useRunData(id);
7777
const parentRunId = useParentRunId(id);
7878
const parentRunData = useRunData(parentRunId ?? '');
79+
const selfRunData = useRunData(id);
7980
const nodesMetaData = useNodesMetadata();
8081
const repetitionName = useMemo(
8182
() => getRepetitionName(parentRunIndex, id, nodesMetaData, operationsInfo),
@@ -120,9 +121,14 @@ const DefaultNode = ({ targetPosition = Position.Top, sourcePosition = Position.
120121

121122
useEffect(() => {
122123
if (!isNullOrUndefined(repetitionRunData)) {
124+
if (selfRunData?.correlation?.actionTrackingId === repetitionRunData?.properties?.correlation?.actionTrackingId) {
125+
// if the correlation id is the same, we don't need to update the repetition run data
126+
return;
127+
}
128+
123129
dispatch(setRepetitionRunData({ nodeId: id, runData: repetitionRunData.properties as LogicAppsV2.WorkflowRunAction }));
124130
}
125-
}, [dispatch, repetitionRunData, id]);
131+
}, [dispatch, repetitionRunData, id, selfRunData?.correlation?.actionTrackingId]);
126132

127133
const { dependencies, loopSources } = useTokenDependencies(id);
128134

libs/designer/src/lib/ui/CustomNodes/ScopeCardNode.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const ScopeCardNode = ({ data, targetPosition = Position.Top, sourcePosition = P
6565
const runData = useRunData(scopeId);
6666
const parentRunId = useParentRunId(scopeId);
6767
const parentRunData = useRunData(parentRunId ?? '');
68+
const selfRunData = useRunData(scopeId);
6869
const nodesMetaData = useNodesMetadata();
6970
const repetitionName = useMemo(
7071
() => getRepetitionName(parentRunIndex, scopeId, nodesMetaData, operationsInfo),
@@ -102,9 +103,14 @@ const ScopeCardNode = ({ data, targetPosition = Position.Top, sourcePosition = P
102103

103104
useEffect(() => {
104105
if (!isNullOrUndefined(repetitionRunData)) {
106+
if (selfRunData?.correlation?.actionTrackingId === repetitionRunData?.properties?.correlation?.actionTrackingId) {
107+
// if the correlation id is the same, we don't need to update the repetition run data
108+
return;
109+
}
110+
105111
dispatch(setRepetitionRunData({ nodeId: scopeId, runData: repetitionRunData.properties as LogicAppsV2.WorkflowRunAction }));
106112
}
107-
}, [dispatch, repetitionRunData, scopeId]);
113+
}, [dispatch, repetitionRunData, scopeId, selfRunData?.correlation?.actionTrackingId]);
108114

109115
const { dependencies, loopSources } = useTokenDependencies(scopeId);
110116
const [{ isDragging }, drag, dragPreview] = useDrag(

playwright.config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
process.env.NODE_ENV = 'development'; // Set before importing Playwright
2+
13
import { defineConfig, devices } from '@playwright/test';
24
import 'dotenv/config';
35

0 commit comments

Comments
 (0)