Skip to content

drag and drop with horizontal movement for level#47824

Open
edewit wants to merge 16 commits into
keycloak:mainfrom
edewit:drop-into-subflow
Open

drag and drop with horizontal movement for level#47824
edewit wants to merge 16 commits into
keycloak:mainfrom
edewit:drop-into-subflow

Conversation

@edewit

@edewit edewit commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

This adds the ability to drag and drop a excution into a subflow

related: #44026
Signed-off-by: Erik Jan de Wit erikjan.dewit@gmail.com

@edewit
edewit requested review from a team as code owners April 7, 2026 14:52
@edewit
edewit force-pushed the drop-into-subflow branch from 6766f41 to 3bb5fde Compare April 14, 2026 14:26
@edewit

edewit commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@ssilvert could you review?

@edewit
edewit force-pushed the drop-into-subflow branch from cbb8f2b to 0013427 Compare May 18, 2026 08:39
@ssilvert

Copy link
Copy Markdown
Contributor

@edewit I'm getting compilation errors when I try to run vite.

@edewit

edewit commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

ahh yeah sorry... there was a merge error

@edewit

edewit commented May 22, 2026

Copy link
Copy Markdown
Contributor Author
image so now we use horizontal movement to determine the level of where to drop

@edewit edewit changed the title drag and drop with shift to drop into subflow drag and drop with horizontal movement for level May 22, 2026
@edewit
edewit force-pushed the drop-into-subflow branch from d6af886 to 63939c6 Compare May 22, 2026 06:34
edewit added 8 commits July 7, 2026 14:54
This adds the ability to drag and drop a excution into a subflow by
holding <shift> when starting the drag.

related: keycloak#44026
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Using dnd-kit directly for more control, now you can drop executions
into subflows and there is a visual indicator that when you do

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
@ssilvert
ssilvert force-pushed the drop-into-subflow branch from 1e33a7a to ecc3071 Compare July 7, 2026 18:54
Copilot AI review requested due to automatic review settings July 7, 2026 18:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the Admin UI authentication flow reordering implementation with a new drag-and-drop approach (dnd-kit) that supports horizontal movement to change nesting level, enabling dropping an execution into a subflow.

Changes:

  • Migrates flow table drag/drop from PatternFly’s DragDrop to @dnd-kit/core with a dedicated grip handle and drag overlay.
  • Adds “horizontal indent” handling during drag to preview/perform nesting into subflows, backed by new ExecutionList.resolveDropTarget() logic and unit tests.
  • Updates Playwright E2E coverage and adds new UI strings + styling for drag affordances and drop indicators.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
js/pnpm-lock.yaml Locks newly added dnd-kit dependencies.
js/apps/admin-ui/package.json Adds dnd-kit dependencies for the Admin UI.
js/apps/admin-ui/src/authentication/FlowDetails.tsx Introduces dnd-kit context, drag sensors, live announcements, and overlay; wires drop preview + execution changes.
js/apps/admin-ui/src/authentication/execution-model.ts Adds drop targeting/preview types and implements drop resolution for reorder vs level-change.
js/apps/admin-ui/src/authentication/components/FlowRow.tsx Adds a per-row grip drag handle, drop preview state rendering, and related data attributes.
js/apps/admin-ui/src/authentication/components/flow-row.css Adds visual styling for drag state, drop lines, nesting highlight, and overlay.
js/apps/admin-ui/src/authentication/components/FlowTitle.tsx Removes the unused “step” type/icon usage to align with the updated row typing.
js/apps/admin-ui/src/authentication/tests/ExecutionList.test.ts Adds unit tests for getChange and resolveDropTarget behavior across nested/collapsed cases.
js/apps/admin-ui/test/autentication/flows.spec.ts Updates existing DnD test to use the grip handle + adds a new horizontal-indent nesting test.
js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties Adds new live-region/message strings for drag instructions and “move into subflow” announcements.
Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +255 to +269
const targetParent =
vertical === "into"
? hover
: clampedLevel > 0
? this.#getParentFromVisualOrder(
clampedLevel,
insertIndex,
visualOrder,
)
: undefined;

const targetParentId =
clampedLevel > 0 && targetParent?.authenticationFlow
? (targetParent.id ?? null)
: null;
Comment on lines +755 to +767
{activeId ? (
<DragOverlayContent
execution={
executionList.order().find((ex) => ex.id === activeId)!
}
targetParentName={
dropInfo.targetParentId
? findExecutionById(dropInfo.targetParentId)
?.displayName
: undefined
}
/>
) : null}
Comment on lines +722 to +724
<Table aria-label={t("flows")} isTreeTable>
<FlowHeader />
<>
Comment on lines +944 to +945
onDragMoveIntoSubflow={{item}} is moved into {{subflow}}
executionDragInstructions=Drag and drop executions to reorder them. Moving them horizontally will change on which level they are inserted.
Comment on lines +87 to +95
.keycloak__authentication__drag-handle:hover {
color: var(--pf-v5-global--Color--100);
background-color: var(--pf-v5-global--BackgroundColor--200);
}

.keycloak__authentication__drag-handle:active {
cursor: grabbing;
}

Comment on lines 89 to 95
"@dagrejs/dagre": "^1.1.8",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@keycloak/keycloak-admin-client": "workspace:*",
"@keycloak/keycloak-ui-shared": "workspace:*",
"@patternfly/patternfly": "^5.4.2",
Instead of using horizontal movement to indicate the level we now use
collapse and expand

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Copilot AI review requested due to automatic review settings July 13, 2026 08:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +90 to +92
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
Comment on lines +943 to +945
onDragMove=Dragging item {{item}}
onDragMoveIntoSubflow={{item}} is moved into {{subflow}}
executionDragInstructions=Drag and drop executions to reorder them. Moving them horizontally will change on which level they are inserted.
Comment on lines +489 to +491
const currentDropInfo = dropInfo;
setDropInfo(emptyDropInfo());

} from "@patternfly/react-icons";
import { Table, Tbody } from "@patternfly/react-table";
import { useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
Comment on lines +409 to +412
const { active } = event;
const { x: pointerX, y: pointerY } = pointer;
const rows = document.querySelectorAll("tr[data-execution-id]");
const hoveredRow = findHoveredRow(
Comment on lines +245 to +249
const collapseSnapshotRef = useRef<Map<string, boolean>>(new Map());
const autoExpandedIdsRef = useRef<Set<string>>(new Set());
const expandTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const pendingExpandIdRef = useRef<string | null>(null);
const collapseRafRef = useRef<number | null>(null);
some small css fixes that fix some adding and removing space

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file


return {
kind: "reorder",
change: this.getChange(dragged, order),
Comment on lines +314 to +317
const needsLevelChange =
vertical === "into" ||
clampedLevel !== draggedLevel ||
currentParent?.id !== targetParent?.id;
Comment on lines +279 to +285
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
}),
);

it("returns null for into on non-subflow", () => {
const list = new ExecutionList(list2Data);
expect(list.resolveDropTarget("1", "1", "into")).toBeNull();
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Copilot AI review requested due to automatic review settings July 14, 2026 09:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +567 to +569
restoreDragState();
setLiveText(t("onDragFinish", { list: dragged.displayName }));
void executeChange(dragged, change);
Comment on lines +277 to +279
dropMode = vertical === "before" ? "reorder-before" : "reorder-after";
insertIndex = vertical === "before" ? hoverIndex : hoverIndex + 1;
clampedLevel = hoverLevel;
Comment on lines +279 to +285
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
}),
);
Comment on lines +230 to 232
await dragExecutionToRow(page, sourceRow, subflowRow, { yRatio: 0.5 });

await assertNotificationMessage(page, "Flow successfully updated");
Comment on lines +567 to +569
restoreDragState();
setLiveText(t("onDragFinish", { list: dragged.displayName }));
void executeChange(dragged, change);
Comment on lines +567 to +569
restoreDragState();
setLiveText(t("onDragFinish", { list: dragged.displayName }));
void executeChange(dragged, change);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +669 to +674
if (originalRequirement) {
await adminClient.authenticationManagement.updateExecution(
{ flow: flow?.alias! },
{ id, requirement: originalRequirement },
);
}
Comment on lines +636 to +640
if (executionFlow.executionList?.length) {
const parentForChildren = { ...executionFlow, id: result.id };
for (const [i, child] of executionFlow.executionList.entries()) {
await executeChange(
child,
Comment on lines +280 to +284
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
}),
Comment on lines +556 to +559
const isNoOp =
change instanceof IndexChange &&
change.oldIndex === change.newIndex &&
resolved.kind === "reorder";
Copilot AI review requested due to automatic review settings July 14, 2026 12:41
@edewit
edewit force-pushed the drop-into-subflow branch from 8cf656a to d97ca75 Compare July 14, 2026 12:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +284 to +290
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
}),
);
Comment on lines +568 to +571
(change instanceof LevelChange &&
change.oldIndex === change.newIndex &&
resolved.kind === "level-change" &&
currentParent?.id === change.parent?.id);
Comment on lines 672 to 675
if (ex.authenticationFlow) {
const executionFlow = ex as ExpandableExecution;
const result =
await adminClient.authenticationManagement.addFlowToFlow({
Comment on lines +684 to +685
if (executionFlow.executionList?.length) {
const parentForChildren = { ...executionFlow, id: result.id };
Comment on lines +143 to +151
const row = page
.locator("tr[data-execution-id]")
.filter({
has:
typeof executionName === "string"
? page.getByTestId(executionName)
: page.getByRole("row", { name: executionName }),
})
.first();
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Copilot AI review requested due to automatic review settings July 14, 2026 13:11
@edewit
edewit force-pushed the drop-into-subflow branch from d97ca75 to 08711df Compare July 14, 2026 13:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +82 to +85
type ExecutionMoveSnapshot = {
config: AuthenticatorConfigRepresentation;
children: ExecutionMoveSnapshot[];
};
Comment on lines +568 to +571
(change instanceof LevelChange &&
change.oldIndex === change.newIndex &&
resolved.kind === "level-change" &&
currentParent?.id === change.parent?.id);
Comment on lines 737 to 742
} catch (error) {
if (silent) {
throw error;
}
addError("updateFlowError", error);
}
Comment on lines +284 to +290
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
}),
);
Comment on lines +143 to +151
const row = page
.locator("tr[data-execution-id]")
.filter({
has:
typeof executionName === "string"
? page.getByTestId(executionName)
: page.getByRole("row", { name: executionName }),
})
.first();

@ssilvert ssilvert left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I tried this out and I think it's getting very close to what we want.

The flows now expand when you hover, but they also need to contract when you hover so that you can change your mind and easily place something outside the flow.

It would also be helpful if the blue line indicating the target drop location looked different from the line showing a selection. Maybe a different color and appearance. For instance, here it's a little hard to tell which solid blue line is the drop indicator.

Image

Lastly, it would be better if The text said exactly where the moved execution will land. In the case above, it would be great if it said, "Cookie will be moved into Copy of browser forms below Username Password Form"

Copilot AI review requested due to automatic review settings July 17, 2026 10:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

Comment on lines +254 to +258
const dragged = this.#findInTree(draggedId);
const hover = visualOrder[hoverIndex];
if (!dragged || draggedId === hoverId) {
return null;
}
Comment on lines +631 to +634
(change instanceof LevelChange &&
change.oldIndex === change.newIndex &&
resolved.kind === "level-change" &&
currentParent?.id === change.parent?.id);
Comment on lines +747 to +759
if (executionFlow.executionList?.length) {
const parentForChildren = { ...executionFlow, id: result.id };
for (const [i, child] of executionFlow.executionList.entries()) {
await executeChange(
child,
{
parent: parentForChildren,
newIndex: i,
oldIndex: i,
},
{ silent: true, snapshot: subtreeSnapshot.children[i] },
);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is a known limitation of the drag and drop support in it's current state

Comment on lines +282 to +288
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
}),
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a larger issue and something that the PF implementation also lacked it's been tracked in this issue #50863

Comment thread js/apps/admin-ui/test/autentication/flow.ts
* The flows now also collapse when choosing another flow
* Used green for drop indicator
* Changed the text in the drag handler to specify entire destination

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Copilot AI review requested due to automatic review settings July 17, 2026 11:54
@edewit
edewit force-pushed the drop-into-subflow branch from 73d55bd to 6ccf92f Compare July 17, 2026 11:54
@edewit
edewit requested a review from ssilvert July 17, 2026 11:55
@edewit

edewit commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@ssilvert right that makes sense updated with your comments

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • js/pnpm-lock.yaml: Generated file

// skipping already deleted execution
}
if ("authenticationFlow" in ex) {
if ("authenticationFlow" in ex && ex.authenticationFlow) {
Comment on lines +747 to +748
if (executionFlow.executionList?.length) {
const parentForChildren = { ...executionFlow, id: result.id };
Comment on lines +93 to +97
const showDropLineAfter =
isDropTarget &&
dropMode === "reorder-after" &&
visualIndex !== undefined &&
dropInfo?.insertIndex === visualIndex + 1;
await assertNotificationMessage(page, "Flow successfully updated");
});

test("drags execution into subflow by dropping on subflow row", async ({
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants