Add double-Alt shortcut to expand and display RequestQueueProgress:

- Update visibility logic with a double-Alt keypress detection.
- Introduce `isShortcutActivated` and `lastAltKeyPressedAtMs` for shortcut tracking.
- Adjust template rendering to depend on the shortcut activation state.
- Add unit tests to validate double-Alt behavior and its impact on visibility and expansion.
This commit is contained in:
Jeppe Bundgaard
2026-03-19 15:25:30 +01:00
parent 159da161d7
commit 23f5fdfcbf
2 changed files with 45 additions and 11 deletions
+26 -1
View File
@@ -16,12 +16,15 @@ const METHOD_ICON_CLASS = Object.freeze({
DELETE: "fa-trash",
DEFAULT: "fa-exchange-alt",
});
const FN_DOUBLE_PRESS_WINDOW_MS = 450;
const isVisible = ref(false);
const isExpanded = ref(REQUEST_QUEUE_CONFIG.inspector.expandedByDefault);
const isShortcutActivated = ref(false);
const nowMs = ref(Date.now());
const pingLatencyMs = ref(null);
const pingIsUnavailable = ref(false);
const lastFnKeyPressedAtMs = ref(0);
let hideTimer = null;
let tickerTimer = null;
@@ -40,6 +43,7 @@ const shouldRenderForBatch = computed(() =>
const activeRequests = computed(() => requestQueueState.activeRequests || []);
const recentRequests = computed(() => requestQueueState.recentRequests || []);
const errorRequests = computed(() => requestQueueState.errorRequests || []);
const shouldRenderComponent = computed(() => isShortcutActivated.value === true);
const appEnvironmentLabel = computed(() => {
const isDev = import.meta.env.VITE_IS_DEV;
return isDev ? "Development" : "Production";
@@ -182,6 +186,24 @@ const toggleExpanded = () => {
isExpanded.value = !isExpanded.value;
};
const handleWindowKeydown = (event) => {
if (event?.key !== "Fn" || event?.repeat === true) {
return;
}
const pressedAt = Date.now();
if (
Number(lastFnKeyPressedAtMs.value) > 0
&& (pressedAt - Number(lastFnKeyPressedAtMs.value)) <= FN_DOUBLE_PRESS_WINDOW_MS
) {
isShortcutActivated.value = true;
isExpanded.value = true;
isVisible.value = true;
}
lastFnKeyPressedAtMs.value = pressedAt;
};
const handleClearErrors = () => {
clearErrorRequests();
};
@@ -216,6 +238,8 @@ watch([hasOutstandingRequests, shouldRenderForBatch, hasStoredInspectorItems], (
}, { immediate: true });
onMounted(() => {
window.addEventListener("keydown", handleWindowKeydown);
tickerTimer = setInterval(() => {
nowMs.value = Date.now();
}, REQUEST_QUEUE_CONFIG.inspector.timerRefreshMs);
@@ -228,6 +252,7 @@ onMounted(() => {
onBeforeUnmount(() => {
clearHideTimer();
window.removeEventListener("keydown", handleWindowKeydown);
if (tickerTimer !== null) {
clearInterval(tickerTimer);
@@ -243,7 +268,7 @@ onBeforeUnmount(() => {
<template>
<transition name="queue-progress-fade">
<div v-if="isVisible" class="request-queue-progress-shell" data-testid="request-queue-progress">
<div v-if="shouldRenderComponent" class="request-queue-progress-shell" data-testid="request-queue-progress">
<div v-if="isExpanded" class="request-queue-progress__expanded-layout">
<aside class="request-queue-progress__side request-queue-progress__side--errors" data-testid="request-queue-errors-box">
<div class="request-queue-progress__side-header">
+19 -10
View File
@@ -32,6 +32,12 @@ const createDeferred = () => {
return { promise, resolve, reject };
};
const triggerAltDoublePress = async () => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Alt" }));
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Alt" }));
await flushMicrotasks();
};
describe("RequestQueueProgress", () => {
const resetSessionUserState = () => {
SessionUser.isSubuser.value = false;
@@ -66,7 +72,7 @@ describe("RequestQueueProgress", () => {
vi.useRealTimers();
});
it("shows request queue progress and hides shortly after completion", async () => {
it("is hidden by default and becomes visible + expanded on double-alt", async () => {
const wrapper = mount(RequestQueueProgress);
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
@@ -78,8 +84,11 @@ describe("RequestQueueProgress", () => {
const requestTwo = enqueueRequest(() => second.promise);
await flushMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
await triggerAltDoublePress();
expect(wrapper.get("[data-testid='request-queue-progress']").text()).toContain("1 active, 1 queued");
expect(wrapper.find("[data-testid='request-queue-progress-details']").exists()).toBe(true);
first.resolve({ status: 200 });
await flushManyMicrotasks();
@@ -95,10 +104,10 @@ describe("RequestQueueProgress", () => {
vi.advanceTimersByTime(2600);
await flushMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
});
it("does not show progress below configured minimum batch size", async () => {
it("stays hidden until double-alt even for small batches", async () => {
const wrapper = mount(RequestQueueProgress);
const onlyRequest = createDeferred();
@@ -106,12 +115,12 @@ describe("RequestQueueProgress", () => {
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
await triggerAltDoublePress();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
onlyRequest.resolve({ status: 200 });
await request;
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
});
it("expands upward with active endpoint details, method icon and timer", async () => {
@@ -123,8 +132,7 @@ describe("RequestQueueProgress", () => {
const requestTwo = enqueueRequest(() => second.promise, { method: "GET", url: "/order/items" });
await flushManyMicrotasks();
await wrapper.get("[data-testid='request-queue-progress-toggle']").trigger("click");
await triggerAltDoublePress();
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-errors-box']").exists()).toBe(true);
@@ -171,7 +179,7 @@ describe("RequestQueueProgress", () => {
const requestTwo = enqueueRequest(() => second.promise, { method: "GET", url: "/subuser-check-2" });
await flushManyMicrotasks();
await wrapper.get("[data-testid='request-queue-progress-toggle']").trigger("click");
await triggerAltDoublePress();
await flushManyMicrotasks();
const userBox = wrapper.get("[data-testid='request-queue-user-box']");
@@ -245,7 +253,7 @@ describe("RequestQueueProgress", () => {
expect(requestQueueState.missingPermissions.length).toBe(1);
expect(requestQueueState.missingPermissions[0].permission).toBe("department_notification_sms_get");
await wrapper.get("[data-testid='request-queue-progress-toggle']").trigger("click");
await triggerAltDoublePress();
await flushManyMicrotasks();
const errorsBox = wrapper.get("[data-testid='request-queue-errors-box']");
@@ -293,13 +301,14 @@ describe("RequestQueueProgress", () => {
await failed;
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
await triggerAltDoublePress();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
vi.advanceTimersByTime(30_000);
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
await wrapper.get("[data-testid='request-queue-progress-toggle']").trigger("click");
await flushManyMicrotasks();
const errorsBox = wrapper.get("[data-testid='request-queue-errors-box']");