fix(path-editor): filter machine options to only show available machine (TRU-91)

The path editor previously rendered all 12 program buttons and all 12 program
wheel positions regardless of whether the current lane had a configured,
non-deleted machine type. This made it possible to author paths for programs
that the machine does not actually support.

Filter pathEditorMachineButtonOptions and pathEditorProgramButtonOptions
based on whether the current lane resolves to an available selfserve_machine_types
row. When the machine is not available, the machine button grid is empty and
the program wheel only exposes the OFF position.
This commit is contained in:
openhands
2026-08-17 16:17:04 +00:00
parent 7d5ec8894c
commit 882bc64fa6
2 changed files with 90 additions and 12 deletions
@@ -1264,14 +1264,19 @@ const createPathEditorQuestion = async () => {
await loadPathOutcomes({ force: true });
};
const pathEditorProgramButtonOptions = computed(() => [
{ id: 0, value: "0", label: "0 = OFF" },
...Array.from({ length: 12 }, (_, index) => ({
id: index + 1,
value: String(index + 1),
label: `Wheel position #${index + 1}`,
})),
]);
const pathEditorProgramButtonOptions = computed(() => {
if (!pathEditorMachineIsAvailable.value) {
return [{ id: 0, value: "0", label: "0 = OFF" }];
}
return [
{ id: 0, value: "0", label: "0 = OFF" },
...Array.from({ length: 12 }, (_, index) => ({
id: index + 1,
value: String(index + 1),
label: `Wheel position #${index + 1}`,
})),
];
});
@@ -1392,16 +1397,56 @@ const pathVerificationSelectedCaseHasRun = computed(() =>
)
);
const pathEditorMachineButtonOptions = computed(() =>
Array.from({ length: 12 }, (_, index) => {
// TRU-91: Filter path editor machine options to only show enabled/available
// machine programs for the current scope. A machine is considered "available"
// when the current lane has a machine_type_id that resolves to a non-deleted
// selfserve_machine_types row in the lookups.
const pathEditorCurrentLaneRow = computed(() => {
const laneId = parseIntOrZero(selectedSimulatorLaneId.value);
if (laneId <= 0) {
return null;
}
return laneRowById(laneId);
});
const pathEditorCurrentMachineType = computed(() => {
const lane = pathEditorCurrentLaneRow.value;
if (!lane) {
return null;
}
const machineTypeId = parseNullableInt(lane.machine_type_id);
if (!machineTypeId) {
return null;
}
const machineTypeRow = lookupRowById("machine_types", machineTypeId);
if (!machineTypeRow) {
return null;
}
if (
machineTypeRow.deleted_at !== null &&
machineTypeRow.deleted_at !== undefined &&
machineTypeRow.deleted_at !== ""
) {
return null;
}
return machineTypeRow;
});
const pathEditorMachineIsAvailable = computed(() => Boolean(pathEditorCurrentMachineType.value));
const pathEditorMachineButtonOptions = computed(() => {
if (!pathEditorMachineIsAvailable.value) {
return [];
}
return Array.from({ length: 12 }, (_, index) => {
const option = taskButtonOptions.find((entry) => entry.id === index);
return {
id: index,
label: option?.description || `Machine button ${index}`,
description: option?.name || `Program ${index + 1}`,
};
})
);
});
});
const pathEditorTaskTitleKeySlug = (key) => String(key || "").replace(/[^a-zA-Z0-9_-]/g, "-");
@@ -0,0 +1,33 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const readSource = (relativePath) => readFileSync(join(process.cwd(), relativePath), "utf8");
describe("self-serve studio path editor machine filter (TRU-91)", () => {
const studioSource = readSource(
"src/views/dashboards/departmentDashboard/modules/self-serve/DepartmentSelfServeStudio.vue"
);
it("defines a path editor machine-availability check tied to the current lane and machine type", () => {
expect(studioSource).toContain("pathEditorCurrentLaneRow");
expect(studioSource).toContain("pathEditorCurrentMachineType");
expect(studioSource).toContain("pathEditorMachineIsAvailable");
});
it("returns an empty machine button list when the machine is not available", () => {
expect(studioSource).toMatch(
/pathEditorMachineButtonOptions[\s\S]{0,400}if\s*\(\s*!pathEditorMachineIsAvailable\.value\s*\)\s*\{\s*return\s*\[\s*\]\s*;\s*\}/
);
});
it("limits program wheel options to the OFF position when the machine is not available", () => {
expect(studioSource).toMatch(
/pathEditorProgramButtonOptions[\s\S]{0,600}if\s*\(\s*!pathEditorMachineIsAvailable\.value\s*\)[\s\S]{0,200}return\s*\[\s*\{\s*id:\s*0,\s*value:\s*"0",\s*label:\s*"0 = OFF"\s*\}\s*\]/
);
});
it("considers a machine type deleted when its deleted_at is set", () => {
expect(studioSource).toMatch(/machineTypeRow\.deleted_at[\s\S]{0,80}return\s+null/);
});
});