Compare commits
11 Commits
97748bea60
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 55986a1965 | |||
| 56bee336db | |||
| aa2661b556 | |||
| 67a723207f | |||
| 3d1de913f8 | |||
| b978071f94 | |||
| 3918755b7c | |||
| 7e052f0d36 | |||
| 0ff03ec0ef | |||
| 0af0ff39d4 | |||
| aeb6d207c5 |
+3
-4
@@ -15,10 +15,9 @@ coverage
|
|||||||
*.local
|
*.local
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
# Cypress
|
# Playwright
|
||||||
cypress.env.json
|
/test-results/
|
||||||
/cypress/videos/
|
/playwright-report/
|
||||||
/cypress/screenshots/
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
vscode
|
vscode
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ and [Tailwind CSS][tailwind].
|
|||||||
| UI | PrimeVue 4, PrimeIcons, SweetAlert2 |
|
| UI | PrimeVue 4, PrimeIcons, SweetAlert2 |
|
||||||
| Visualization | Cytoscape.js (process maps), Chart.js (charts) |
|
| Visualization | Cytoscape.js (process maps), Chart.js (charts) |
|
||||||
| HTTP | Axios with JWT refresh token handling |
|
| HTTP | Axios with JWT refresh token handling |
|
||||||
| Testing | Vitest 4 (unit/component), Cypress 15 (E2E) |
|
| Testing | Vitest 4 (unit/component), [Playwright][playwright] (E2E), [MSW][msw] (API mocking) |
|
||||||
| Linting | ESLint, Prettier |
|
| Linting | ESLint, Prettier |
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ Copy `.env` to `.env.local` and set the backend API URL:
|
|||||||
cp .env .env.local
|
cp .env .env.local
|
||||||
```
|
```
|
||||||
|
|
||||||
```dotenv
|
```sh
|
||||||
# .env.local
|
# .env.local
|
||||||
VUE_APP_API_URL = "http://localhost:8000"
|
VUE_APP_API_URL = "http://localhost:8000"
|
||||||
```
|
```
|
||||||
@@ -80,18 +80,18 @@ npx vitest run
|
|||||||
|
|
||||||
### Run E2E tests
|
### Run E2E tests
|
||||||
|
|
||||||
Build first, then run [Cypress][cypress] against the preview
|
Build with MSW enabled first, then run [Playwright][playwright]:
|
||||||
server:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run build
|
npm run build:e2e
|
||||||
npm run test:e2e
|
npm run test:e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
For interactive E2E development with the Vite dev server:
|
For interactive E2E development with the Playwright UI:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run test:e2e:dev
|
npm run build:e2e
|
||||||
|
npm run test:e2e:ui
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -169,6 +169,7 @@ Code quality improvements assisted by [Claude Code][claude-code].
|
|||||||
[cytoscape]: https://js.cytoscape.org/
|
[cytoscape]: https://js.cytoscape.org/
|
||||||
[chartjs]: https://www.chartjs.org/
|
[chartjs]: https://www.chartjs.org/
|
||||||
[nodejs]: https://nodejs.org/
|
[nodejs]: https://nodejs.org/
|
||||||
[cypress]: https://www.cypress.io/
|
[playwright]: https://playwright.dev/
|
||||||
|
[msw]: https://mswjs.io/
|
||||||
[typedoc]: https://typedoc.org/
|
[typedoc]: https://typedoc.org/
|
||||||
[claude-code]: https://claude.ai/claude-code
|
[claude-code]: https://claude.ai/claude-code
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2023-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/8/12
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/3/5
|
|
||||||
/**
|
|
||||||
* @module cypress.config
|
|
||||||
* Cypress E2E test configuration with viewport
|
|
||||||
* settings and base URL.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { defineConfig } = require("cypress");
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
|
||||||
defaultCommandTimeout: 6000,
|
|
||||||
viewportWidth: 1280,
|
|
||||||
viewportHeight: 720,
|
|
||||||
e2e: {
|
|
||||||
baseUrl: "http://localhost:4173",
|
|
||||||
specPattern: "cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}",
|
|
||||||
},
|
|
||||||
includeShadowDom: true,
|
|
||||||
env: {},
|
|
||||||
});
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Account Management", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays user list on account admin page", () => {
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
// Should display users from fixture
|
|
||||||
cy.contains("Test Admin").should("exist");
|
|
||||||
cy.contains("Alice Wang").should("exist");
|
|
||||||
cy.contains("Bob Chen").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows active/inactive status badges", () => {
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
// The user list should show status indicators
|
|
||||||
cy.contains("testadmin").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("navigates to my-account page", () => {
|
|
||||||
cy.visit("/my-account");
|
|
||||||
cy.wait("@getMyAccount");
|
|
||||||
cy.url().should("include", "/my-account");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/07/03
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../../support/intercept";
|
|
||||||
|
|
||||||
const MSG_ACCOUNT_NOT_UNIQUE = "Account has already been registered.";
|
|
||||||
|
|
||||||
describe("Account duplication check.", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("When an account already exists, show error message on confirm.", () => {
|
|
||||||
const testAccountName = "000000";
|
|
||||||
|
|
||||||
// First creation: account doesn't exist yet
|
|
||||||
cy.intercept("GET", "/api/users/000000", {
|
|
||||||
statusCode: 404,
|
|
||||||
body: { detail: "Not found" },
|
|
||||||
}).as("checkNewUser");
|
|
||||||
|
|
||||||
cy.contains("button", "Create New").should("be.visible").click();
|
|
||||||
cy.get("#input_account_field").type(testAccountName);
|
|
||||||
cy.get("#input_name_field").type(testAccountName);
|
|
||||||
cy.get("#input_first_pwd").type(testAccountName);
|
|
||||||
cy.get(".checkbox-and-text").first().find("div").first().click();
|
|
||||||
|
|
||||||
cy.contains("button", "Confirm")
|
|
||||||
.should("be.visible")
|
|
||||||
.and("be.enabled")
|
|
||||||
.click();
|
|
||||||
cy.wait("@postUser");
|
|
||||||
cy.contains("Account added").should("be.visible");
|
|
||||||
|
|
||||||
// Second creation: now account exists — override to return 200
|
|
||||||
cy.intercept("GET", "/api/users/000000", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: {
|
|
||||||
username: "000000",
|
|
||||||
name: "000000",
|
|
||||||
is_admin: false,
|
|
||||||
is_active: true,
|
|
||||||
roles: [],
|
|
||||||
},
|
|
||||||
}).as("checkExistingUser");
|
|
||||||
|
|
||||||
cy.contains("button", "Create New").should("be.visible").click();
|
|
||||||
cy.get("#input_account_field").type(testAccountName);
|
|
||||||
cy.get("#input_name_field").type(testAccountName);
|
|
||||||
cy.get("#input_first_pwd").type(testAccountName);
|
|
||||||
cy.get(".checkbox-and-text").first().find("div").first().click();
|
|
||||||
|
|
||||||
cy.contains("button", "Confirm")
|
|
||||||
.should("be.visible")
|
|
||||||
.and("be.enabled")
|
|
||||||
.click();
|
|
||||||
cy.contains(MSG_ACCOUNT_NOT_UNIQUE).should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/07/02
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../../support/intercept";
|
|
||||||
|
|
||||||
describe("Password validation on create account.", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("When password is too short, confirm button stays disabled.", () => {
|
|
||||||
cy.contains("button", "Create New").should("be.visible").click();
|
|
||||||
|
|
||||||
cy.get("#input_account_field").type("unit-test-0001");
|
|
||||||
cy.get("#input_name_field").type("unit-test-0001");
|
|
||||||
// Password shorter than 6 characters
|
|
||||||
cy.get("#input_first_pwd").type("aaa");
|
|
||||||
|
|
||||||
cy.contains("button", "Confirm").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("When password meets minimum length, confirm button enables.", () => {
|
|
||||||
cy.contains("button", "Create New").should("be.visible").click();
|
|
||||||
|
|
||||||
cy.get("#input_account_field").type("unit-test-0001");
|
|
||||||
cy.get("#input_name_field").type("unit-test-0001");
|
|
||||||
cy.get("#input_first_pwd").type("aaaaaa");
|
|
||||||
|
|
||||||
cy.contains("button", "Confirm").should("be.enabled");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/07/02
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../../support/intercept";
|
|
||||||
|
|
||||||
describe("Create an Account", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
// Override: new usernames should return 404 (account doesn't exist yet)
|
|
||||||
cy.intercept("GET", "/api/users/unit-test-*", {
|
|
||||||
statusCode: 404,
|
|
||||||
body: { detail: "Not found" },
|
|
||||||
}).as("checkNewUser");
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new account with admin role; should show saved message.", () => {
|
|
||||||
cy.contains("button", "Create New").should("be.visible").click();
|
|
||||||
|
|
||||||
cy.get("#input_account_field").type("unit-test-0001");
|
|
||||||
cy.get("#input_name_field").type("unit-test-0001");
|
|
||||||
cy.get("#input_first_pwd").type("aaaaaa");
|
|
||||||
cy.get(".checkbox-and-text").first().find("div").first().click();
|
|
||||||
|
|
||||||
cy.contains("button", "Confirm")
|
|
||||||
.should("be.visible")
|
|
||||||
.and("be.enabled")
|
|
||||||
.click();
|
|
||||||
cy.wait("@postUser");
|
|
||||||
cy.contains("Account added").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Confirm button is disabled when required fields are empty.", () => {
|
|
||||||
cy.contains("button", "Create New").should("be.visible").click();
|
|
||||||
cy.get("#input_account_field").type("test");
|
|
||||||
cy.contains("button", "Confirm").should("be.disabled");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/07/03
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../../support/intercept";
|
|
||||||
|
|
||||||
describe("Delete an Account", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Delete button opens confirmation modal and deletes on confirm.", () => {
|
|
||||||
cy.get("img.delete-account").first().click();
|
|
||||||
cy.contains("ARE YOU SURE TO DELETE").should("be.visible");
|
|
||||||
cy.get("#sure_to_delete_acct_btn").click();
|
|
||||||
cy.wait("@deleteUser");
|
|
||||||
cy.contains("Account deleted").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cancel button closes the delete confirmation modal.", () => {
|
|
||||||
cy.get("img.delete-account").first().click();
|
|
||||||
cy.contains("ARE YOU SURE TO DELETE").should("be.visible");
|
|
||||||
cy.get("#calcel_delete_acct_btn").click();
|
|
||||||
cy.contains("ARE YOU SURE TO DELETE").should("not.exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/07/03
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../../support/intercept";
|
|
||||||
|
|
||||||
const MODAL_TITLE_ACCOUNT_EDIT = "Account Edit";
|
|
||||||
const MSG_ACCOUNT_EDITED = "Saved";
|
|
||||||
|
|
||||||
describe("Edit an account", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Edit an account; modify name and see saved message.", () => {
|
|
||||||
cy.get(".btn-edit").first().click();
|
|
||||||
cy.wait("@getUserDetail");
|
|
||||||
|
|
||||||
cy.contains("h1", MODAL_TITLE_ACCOUNT_EDIT).should("exist");
|
|
||||||
cy.get("#input_name_field").clear();
|
|
||||||
cy.get("#input_name_field").type("Updated Name");
|
|
||||||
|
|
||||||
cy.contains("button", "Confirm").should("be.visible").and("be.enabled");
|
|
||||||
cy.contains("button", "Confirm").click();
|
|
||||||
cy.wait("@putUser");
|
|
||||||
cy.contains(MSG_ACCOUNT_EDITED).should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Account Management CRUD", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Create New button", () => {
|
|
||||||
cy.get("#create_new_acct_btn").should("exist");
|
|
||||||
cy.get("#create_new_acct_btn").should("contain", "Create New");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("opens create new account modal", () => {
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get("#modal_account_edit_or_create_new").should("be.visible");
|
|
||||||
// Should show account, name, password fields
|
|
||||||
cy.get("#input_account_field").should("exist");
|
|
||||||
cy.get("#input_name_field").should("exist");
|
|
||||||
cy.get("#input_first_pwd").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("create account confirm is disabled when fields are empty", () => {
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
cy.get(".confirm-btn").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("create account confirm enables when fields are filled", () => {
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
cy.get("#input_account_field").type("newuser");
|
|
||||||
cy.get("#input_name_field").type("New User");
|
|
||||||
cy.get("#input_first_pwd").type("password1234");
|
|
||||||
cy.get(".confirm-btn").should("not.be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cancel button closes the modal", () => {
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get(".cancel-btn").click();
|
|
||||||
cy.get("#modal_container").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("close (X) button closes the modal", () => {
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get('img[alt="X"]').click();
|
|
||||||
cy.get("#modal_container").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("double-click username opens account info modal", () => {
|
|
||||||
// Double-click on the first account username
|
|
||||||
cy.get(".account-cell").first().dblclick();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete button opens delete confirmation modal", () => {
|
|
||||||
// Click the delete icon for a non-current user
|
|
||||||
cy.get(".delete-account").first().click();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get("#modal_delete_acct_alert").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete modal has Yes and No buttons", () => {
|
|
||||||
cy.get(".delete-account").first().click();
|
|
||||||
cy.get("#calcel_delete_acct_btn").should("exist");
|
|
||||||
cy.get("#sure_to_delete_acct_btn").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete modal No button closes the modal", () => {
|
|
||||||
cy.get(".delete-account").first().click();
|
|
||||||
cy.get("#calcel_delete_acct_btn").click();
|
|
||||||
cy.get("#modal_container").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows checkboxes for Set as Admin and Activate in create modal", () => {
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
cy.get("#account_create_checkboxes_section").should("be.visible");
|
|
||||||
cy.contains("Set as admin.").should("exist");
|
|
||||||
cy.contains("Activate now.").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("search bar filters user list", () => {
|
|
||||||
// Search filters by username, not display name
|
|
||||||
cy.get("#input_search").type("user1");
|
|
||||||
cy.get('img[alt="search"]').click();
|
|
||||||
// Should only show user1 (Alice Wang)
|
|
||||||
cy.contains("user1").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Account Info Modal", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("double-click username opens info modal with user data", () => {
|
|
||||||
cy.get(".account-cell").first().dblclick();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get("#acct_info_user_name").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("info modal shows Account Information header", () => {
|
|
||||||
cy.get(".account-cell").first().dblclick();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.contains("Account Information").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("info modal shows account visit info", () => {
|
|
||||||
cy.get(".account-cell").first().dblclick();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get("#account_visit_info").should("exist");
|
|
||||||
cy.get("#account_visit_info").should("contain", "Account:");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("info modal can be closed via X button", () => {
|
|
||||||
cy.get(".account-cell").first().dblclick();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get('img[alt="X"]').click();
|
|
||||||
cy.get("#modal_container").should("not.exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// chiayin.kuo@dsp.im (chiayin), 2024/02/22
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/05/30
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Compare", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.contains("li", "COMPARE").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Compare dropdown sorting options", () => {
|
|
||||||
const expectedOptions = [
|
|
||||||
"By File Name (A to Z)",
|
|
||||||
"By File Name (Z to A)",
|
|
||||||
"By Dependency (A to Z)",
|
|
||||||
"By Dependency (Z to A)",
|
|
||||||
"By File Type (A to Z)",
|
|
||||||
"By File Type (Z to A)",
|
|
||||||
"By Last Update (A to Z)",
|
|
||||||
"By Last Update (Z to A)",
|
|
||||||
];
|
|
||||||
|
|
||||||
cy.get(".p-select").click();
|
|
||||||
cy.get(".p-select-list")
|
|
||||||
.find(".p-select-option")
|
|
||||||
.then(($options) => {
|
|
||||||
const actualOptions = $options
|
|
||||||
.map((index, elem) =>
|
|
||||||
Cypress.$(elem).find(".p-select-option-label").text(),
|
|
||||||
)
|
|
||||||
.get();
|
|
||||||
expect(actualOptions).to.deep.equal(expectedOptions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Grid cards are rendered for compare file selection", () => {
|
|
||||||
cy.get("#compareGridCards").find("li").should("have.length.greaterThan", 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Compare button is disabled until two files are dragged", () => {
|
|
||||||
cy.contains("button", "Compare").should("be.disabled");
|
|
||||||
cy.get("#compareFile0").drag("#primaryDragCard");
|
|
||||||
cy.get("#compareFile1").drag("#secondaryDragCard");
|
|
||||||
cy.contains("button", "Compare").should("be.enabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Enter Compare dashboard and see charts", () => {
|
|
||||||
cy.get("#compareFile0").drag("#primaryDragCard");
|
|
||||||
cy.get("#compareFile1").drag("#secondaryDragCard");
|
|
||||||
cy.contains("button", "Compare").click();
|
|
||||||
cy.wait("@getCompare");
|
|
||||||
cy.url().should("include", "compare");
|
|
||||||
|
|
||||||
// Assert chart title spans are visible
|
|
||||||
cy.contains("span", "Average Cycle Time").should("exist");
|
|
||||||
cy.contains("span", "Cycle Efficiency").should("exist");
|
|
||||||
cy.contains("span", "Average Processing Time").should("exist");
|
|
||||||
cy.contains("span", "Average Processing Time by Activity").should("exist");
|
|
||||||
cy.contains("span", "Average Waiting Time").should("exist");
|
|
||||||
cy.contains("span", "Average Waiting Time between Activity").should(
|
|
||||||
"exist",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Compare State button exists on dashboard", () => {
|
|
||||||
cy.get("#compareFile0").drag("#primaryDragCard");
|
|
||||||
cy.get("#compareFile1").drag("#secondaryDragCard");
|
|
||||||
cy.contains("button", "Compare").click();
|
|
||||||
cy.wait("@getCompare");
|
|
||||||
|
|
||||||
cy.get("#compareState").should("exist").and("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Sidebar shows time usage and frequency sections", () => {
|
|
||||||
cy.get("#compareFile0").drag("#primaryDragCard");
|
|
||||||
cy.get("#compareFile1").drag("#secondaryDragCard");
|
|
||||||
cy.contains("button", "Compare").click();
|
|
||||||
cy.wait("@getCompare");
|
|
||||||
|
|
||||||
cy.get("aside").should("exist");
|
|
||||||
cy.get("aside li").should("have.length.greaterThan", 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Discover Conformance Page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/discover/log/297310264/conformance");
|
|
||||||
cy.wait("@getLogCheckParams");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("page loads and loading overlay disappears", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays Rule Settings sidebar", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Rule Settings").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays Conformance Checking Results heading", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Conformance Checking Results").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays rule type radio options", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Have activity").should("be.visible");
|
|
||||||
cy.contains("Activity sequence").should("be.visible");
|
|
||||||
cy.contains("Activity duration").should("be.visible");
|
|
||||||
cy.contains("Processing time").should("be.visible");
|
|
||||||
cy.contains("Waiting time").should("be.visible");
|
|
||||||
cy.contains("Cycle time").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays Clear and Apply buttons", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("button", "Clear").should("be.visible");
|
|
||||||
cy.contains("button", "Apply").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays Activity list area", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Activity list").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays default placeholder values in results", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Conformance Rate").should("be.visible");
|
|
||||||
cy.contains("Cases").should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Discover Map Page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/discover/log/297310264/map");
|
|
||||||
cy.wait("@getDiscover");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("page loads and cytoscape container exists", () => {
|
|
||||||
cy.get("#cy").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays left sidebar buttons", () => {
|
|
||||||
// Visualization Setting, Filter, Traces buttons
|
|
||||||
cy.get(".material-symbols-outlined")
|
|
||||||
.contains("track_changes")
|
|
||||||
.should("exist");
|
|
||||||
cy.get(".material-symbols-outlined").contains("tornado").should("exist");
|
|
||||||
cy.get(".material-symbols-outlined").contains("rebase").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays right sidebar Summary button", () => {
|
|
||||||
cy.get("#sidebar_state").should("exist");
|
|
||||||
cy.get("#iconState").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Visualization Setting button toggles sidebar", () => {
|
|
||||||
// Click the track_changes icon (Visualization Setting)
|
|
||||||
cy.contains("span.material-symbols-outlined", "track_changes")
|
|
||||||
.parent("li")
|
|
||||||
.click();
|
|
||||||
// SidebarView should open
|
|
||||||
cy.contains("Visualization Setting").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Summary button toggles sidebar", () => {
|
|
||||||
cy.get("#iconState").click();
|
|
||||||
// SidebarState should open with insights/stats
|
|
||||||
cy.contains("Summary").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Traces button toggles sidebar", () => {
|
|
||||||
cy.contains("span.material-symbols-outlined", "rebase")
|
|
||||||
.parent("li")
|
|
||||||
.click();
|
|
||||||
// SidebarTraces should open
|
|
||||||
cy.contains("Traces").should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Discover Performance Page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/discover/log/297310264/performance");
|
|
||||||
cy.wait("@getPerformance");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("page loads and loading overlay disappears", () => {
|
|
||||||
// Loading overlay should not be visible after data loads
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays Time Usage sidebar section", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Time Usage").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays Frequency sidebar section", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Frequency").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays sidebar navigation items", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Cycle Time & Efficiency").should("be.visible");
|
|
||||||
cy.contains("Processing Time").should("be.visible");
|
|
||||||
cy.contains("Waiting Time").should("be.visible");
|
|
||||||
cy.contains("Number of Cases").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays chart titles", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Average Cycle Time").should("be.visible");
|
|
||||||
cy.contains("Cycle Efficiency").should("be.visible");
|
|
||||||
cy.contains("Average Processing Time").should("be.visible");
|
|
||||||
cy.contains("Average Processing Time by Activity").should("be.visible");
|
|
||||||
cy.contains("Average Waiting Time").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays frequency chart titles", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("New Cases").should("be.visible");
|
|
||||||
cy.contains("Number of Cases by Activity").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders canvas elements for charts", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
// Chart.js renders into canvas elements
|
|
||||||
cy.get("canvas").should("have.length.at.least", 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sidebar navigation scrolls to section", () => {
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
// Click on "Waiting Time" in sidebar
|
|
||||||
cy.contains("li", "Waiting Time").click();
|
|
||||||
// The Waiting Time section should be in view
|
|
||||||
cy.get("#waitingTime").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Discover Tab Navigation", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("navigating from Map page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("/discover/log/297310264/map");
|
|
||||||
cy.wait("@getDiscover");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows DISCOVER heading and MAP/CONFORMANCE/PERFORMANCE tabs", () => {
|
|
||||||
cy.get("#nav_bar").contains("DISCOVER").should("be.visible");
|
|
||||||
cy.get(".nav-item").should("have.length", 3);
|
|
||||||
cy.get(".nav-item").eq(0).should("contain", "MAP");
|
|
||||||
cy.get(".nav-item").eq(1).should("contain", "CONFORMANCE");
|
|
||||||
cy.get(".nav-item").eq(2).should("contain", "PERFORMANCE");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking PERFORMANCE tab navigates to performance page", () => {
|
|
||||||
cy.get(".nav-item").contains("PERFORMANCE").click();
|
|
||||||
cy.url().should("include", "/performance");
|
|
||||||
cy.wait("@getPerformance");
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Time Usage").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking CONFORMANCE tab navigates to conformance page", () => {
|
|
||||||
cy.get(".nav-item").contains("CONFORMANCE").click();
|
|
||||||
cy.url().should("include", "/conformance");
|
|
||||||
cy.wait("@getLogCheckParams");
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Rule Settings").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows back arrow to return to Files", () => {
|
|
||||||
cy.get("#backPage").should("exist");
|
|
||||||
cy.get("#backPage").should("have.attr", "href", "/files");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("navigating from Performance page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("/discover/log/297310264/performance");
|
|
||||||
cy.wait("@getPerformance");
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking MAP tab navigates to map page", () => {
|
|
||||||
cy.get(".nav-item").contains("MAP").click();
|
|
||||||
cy.url().should("include", "/map");
|
|
||||||
cy.wait("@getDiscover");
|
|
||||||
cy.get("#cy").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking CONFORMANCE tab navigates to conformance page", () => {
|
|
||||||
cy.get(".nav-item").contains("CONFORMANCE").click();
|
|
||||||
cy.url().should("include", "/conformance");
|
|
||||||
cy.wait("@getLogCheckParams");
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Rule Settings").should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("navigating from Conformance page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("/discover/log/297310264/conformance");
|
|
||||||
cy.wait("@getLogCheckParams");
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking MAP tab navigates to map page", () => {
|
|
||||||
cy.get(".nav-item").contains("MAP").click();
|
|
||||||
cy.url().should("include", "/map");
|
|
||||||
cy.wait("@getDiscover");
|
|
||||||
cy.get("#cy").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking PERFORMANCE tab navigates to performance page", () => {
|
|
||||||
cy.get(".nav-item").contains("PERFORMANCE").click();
|
|
||||||
cy.url().should("include", "/performance");
|
|
||||||
cy.wait("@getPerformance");
|
|
||||||
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
|
|
||||||
cy.contains("Time Usage").should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Edge Cases", () => {
|
|
||||||
describe("Empty states", () => {
|
|
||||||
it("files page handles empty file list", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
// Override files intercept with empty array
|
|
||||||
cy.intercept("GET", "/api/files", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: [],
|
|
||||||
}).as("getEmptyFiles");
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getEmptyFiles");
|
|
||||||
// Table should exist but have no file data
|
|
||||||
cy.get("table").should("exist");
|
|
||||||
cy.contains("sample-process.xes").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("account admin handles empty user list", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.intercept("GET", "/api/users", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: [],
|
|
||||||
}).as("getEmptyUsers");
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getEmptyUsers");
|
|
||||||
// Create New button should still work
|
|
||||||
cy.get("#create_new_acct_btn").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Authentication guard", () => {
|
|
||||||
it("unauthenticated user is redirected to login", () => {
|
|
||||||
// No loginWithFixtures - not logged in
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("unauthenticated user cannot access account-admin", () => {
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("unauthenticated user cannot access my-account", () => {
|
|
||||||
cy.visit("/my-account");
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Login validation", () => {
|
|
||||||
it("shows error on failed login", () => {
|
|
||||||
cy.intercept("POST", "/api/oauth/token", {
|
|
||||||
statusCode: 401,
|
|
||||||
body: { detail: "Invalid credentials" },
|
|
||||||
}).as("failedLogin");
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("#account").type("wrong");
|
|
||||||
cy.get("#password").type("wrong");
|
|
||||||
cy.get("form").submit();
|
|
||||||
cy.wait("@failedLogin");
|
|
||||||
// Should stay on login page
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Account creation validation", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
cy.get("#create_new_acct_btn").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("confirm stays disabled with only account field filled", () => {
|
|
||||||
cy.get("#input_account_field").type("newuser");
|
|
||||||
cy.get(".confirm-btn").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("confirm stays disabled with only name field filled", () => {
|
|
||||||
cy.get("#input_name_field").type("New User");
|
|
||||||
cy.get(".confirm-btn").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("confirm stays disabled with only password field filled", () => {
|
|
||||||
cy.get("#input_first_pwd").type("password1234");
|
|
||||||
cy.get(".confirm-btn").should("be.disabled");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("File Operations", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("file list table has sortable columns", () => {
|
|
||||||
// Check that table headers exist with expected columns
|
|
||||||
cy.get("table").within(() => {
|
|
||||||
cy.contains("th", "Name").should("exist");
|
|
||||||
cy.contains("th", "Dependency").should("exist");
|
|
||||||
cy.contains("th", "File Type").should("exist");
|
|
||||||
cy.contains("th", "Owner").should("exist");
|
|
||||||
cy.contains("th", "Last Update").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking column header sorts the table", () => {
|
|
||||||
// Click "Name" header to sort
|
|
||||||
cy.contains("th", "Name").click();
|
|
||||||
// After sorting, table should still have data
|
|
||||||
cy.get("table tbody tr").should("have.length.greaterThan", 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("table rows show file data from fixture", () => {
|
|
||||||
cy.get("table tbody").within(() => {
|
|
||||||
cy.contains("sample-process.xes").should("exist");
|
|
||||||
cy.contains("filtered-sample").should("exist");
|
|
||||||
cy.contains("production-log.csv").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("table shows owner names", () => {
|
|
||||||
cy.get("table tbody").within(() => {
|
|
||||||
cy.contains("Test Admin").should("exist");
|
|
||||||
cy.contains("Alice Wang").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("table shows file types", () => {
|
|
||||||
cy.get("table tbody").within(() => {
|
|
||||||
cy.contains("log").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("right-click on file row shows context menu", () => {
|
|
||||||
// PrimeVue DataTable with contextmenu
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
// Context menu behavior depends on implementation
|
|
||||||
// Just verify the right-click doesn't break anything
|
|
||||||
cy.get("table tbody tr").should("have.length.greaterThan", 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("grid view shows file cards", () => {
|
|
||||||
// Switch to grid view
|
|
||||||
cy.get("svg").parent("li.cursor-pointer").last().click();
|
|
||||||
// Grid cards should be visible
|
|
||||||
cy.get("li[title]").should("have.length.greaterThan", 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Import button opens upload modal", () => {
|
|
||||||
cy.get("#import_btn").click();
|
|
||||||
// Upload modal should appear
|
|
||||||
cy.get("#import_btn").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Files Page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays the file list after login", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.contains("h2", "All Files").should("exist");
|
|
||||||
// Should display file names from fixture
|
|
||||||
cy.contains("sample-process.xes").should("exist");
|
|
||||||
cy.contains("filtered-sample").should("exist");
|
|
||||||
cy.contains("production-log.csv").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Recently Used section", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.contains("h2", "Recently Used").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("switches to DISCOVER tab", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.contains(".nav-item", "DISCOVER").click();
|
|
||||||
// DISCOVER tab shows filtered file types
|
|
||||||
cy.contains("h2", "All Files").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("switches to COMPARE tab and shows drag zones", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.contains(".nav-item", "COMPARE").click();
|
|
||||||
cy.contains("Performance Comparison").should("exist");
|
|
||||||
cy.contains("Drag and drop a file here").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Import button on FILES tab", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.get("#import_btn").should("contain", "Import");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can switch between list and grid view", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
// DataTable (list view) should be visible by default
|
|
||||||
cy.get("table").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("double-click file navigates to discover page", () => {
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
// Double-click the first file row in the table
|
|
||||||
// The actual route depends on file type (log→map, log-check→conformance, etc.)
|
|
||||||
cy.get("table tbody tr").first().dblclick();
|
|
||||||
cy.url().should("include", "/discover");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Files Page - COMPARE Tab", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
// Switch to COMPARE tab
|
|
||||||
cy.contains("li", "COMPARE").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Performance Comparison heading", () => {
|
|
||||||
cy.contains("h2", "Performance Comparison").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows two drag-and-drop slots", () => {
|
|
||||||
cy.get("#primaryDragCard").should("exist");
|
|
||||||
cy.get("#secondaryDragCard").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("drag slots show placeholder text", () => {
|
|
||||||
cy.get("#primaryDragCard").should("contain", "Drag and drop a file here");
|
|
||||||
cy.get("#secondaryDragCard").should("contain", "Drag and drop a file here");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Compare button is disabled when no files are dragged", () => {
|
|
||||||
cy.contains("button", "Compare").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows sorting dropdown", () => {
|
|
||||||
cy.get(".p-select").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("grid cards display file names", () => {
|
|
||||||
cy.get("#compareGridCards").should("exist");
|
|
||||||
cy.get("#compareGridCards li").should("have.length.greaterThan", 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking sorting dropdown shows sort options", () => {
|
|
||||||
cy.get(".p-select").click();
|
|
||||||
cy.get(".p-select-list").should("be.visible");
|
|
||||||
cy.contains(".p-select-option", "By File Name").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Files to Discover Entry Flow", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("double-click table row to enter Discover", () => {
|
|
||||||
it("double-click log file navigates to Map page", () => {
|
|
||||||
// Target the Name column (has class .fileName) to avoid matching Dependency column
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").parent("tr").dblclick();
|
|
||||||
cy.url().should("include", "/discover/log/1/map");
|
|
||||||
cy.wait("@getDiscover");
|
|
||||||
cy.get("#cy").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("double-click filter file navigates to Map page", () => {
|
|
||||||
cy.contains("td.fileName", "filtered-sample").parent("tr").dblclick();
|
|
||||||
cy.url().should("include", "/discover/filter/10/map");
|
|
||||||
cy.wait("@getFilterDiscover");
|
|
||||||
cy.get("#cy").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("double-click grid card to enter Discover", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// Switch to grid view
|
|
||||||
cy.get("svg").parent("li.cursor-pointer").last().click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("double-click log file grid card navigates to Map page", () => {
|
|
||||||
// Use last() to target the All Files grid section (not Recently Used)
|
|
||||||
cy.get('li[title="sample-process.xes"]').last().dblclick();
|
|
||||||
cy.url().should("include", "/discover/log/1/map");
|
|
||||||
cy.wait("@getDiscover");
|
|
||||||
cy.get("#cy").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("DISCOVER tab filters files", () => {
|
|
||||||
it("clicking DISCOVER tab shows only Log, Filter, and Rule files", () => {
|
|
||||||
cy.get(".nav-item").contains("DISCOVER").click();
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").should("exist");
|
|
||||||
cy.contains("td.fileName", "filtered-sample").should("exist");
|
|
||||||
cy.contains("td.fileName", "conformance-check-1").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Navbar state after entering Discover", () => {
|
|
||||||
it("shows DISCOVER heading and tabs after entering from Files", () => {
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").parent("tr").dblclick();
|
|
||||||
cy.url().should("include", "/discover/");
|
|
||||||
cy.get("#nav_bar").contains("DISCOVER").should("be.visible");
|
|
||||||
cy.get(".nav-item").contains("MAP").should("exist");
|
|
||||||
cy.get(".nav-item").contains("CONFORMANCE").should("exist");
|
|
||||||
cy.get(".nav-item").contains("PERFORMANCE").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows back arrow pointing to /files", () => {
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").parent("tr").dblclick();
|
|
||||||
cy.url().should("include", "/discover/");
|
|
||||||
cy.get("#backPage").should("have.attr", "href", "/files");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["es5", "dom"],
|
|
||||||
"types": ["cypress"]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./**/*",
|
|
||||||
"../support/**/*",
|
|
||||||
"/node_modules/cypress",
|
|
||||||
"cypress/**/*.js",],
|
|
||||||
"experimentalShadowDomSupport": true
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { setupApiIntercepts } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Login Flow", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setupApiIntercepts();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the login form", () => {
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("h2").should("contain", "LOGIN");
|
|
||||||
cy.get("#account").should("exist");
|
|
||||||
cy.get("#password").should("exist");
|
|
||||||
cy.get("#login_btn_main_btn").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("login button is disabled when fields are empty", () => {
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("#login_btn_main_btn").should("be.disabled");
|
|
||||||
|
|
||||||
// Only username filled — still disabled
|
|
||||||
cy.get("#account").type("testuser");
|
|
||||||
cy.get("#login_btn_main_btn").should("be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("login button enables when both fields are filled", () => {
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("#account").type("testadmin");
|
|
||||||
cy.get("#password").type("password123");
|
|
||||||
cy.get("#login_btn_main_btn").should("not.be.disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("successful login redirects to /files", () => {
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("#account").type("testadmin");
|
|
||||||
cy.get("#password").type("password123");
|
|
||||||
cy.get("#login_btn_main_btn").click();
|
|
||||||
|
|
||||||
cy.wait("@postToken");
|
|
||||||
cy.url().should("include", "/files");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("failed login shows error message", () => {
|
|
||||||
// Override the token intercept to return 401
|
|
||||||
cy.intercept("POST", "/api/oauth/token", {
|
|
||||||
statusCode: 401,
|
|
||||||
body: { detail: "Incorrect username or password" },
|
|
||||||
}).as("postTokenFail");
|
|
||||||
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("#account").type("wronguser");
|
|
||||||
cy.get("#password").type("wrongpass");
|
|
||||||
cy.get("#login_btn_main_btn").click();
|
|
||||||
|
|
||||||
cy.wait("@postTokenFail");
|
|
||||||
cy.contains("Incorrect account or password").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("toggles password visibility", () => {
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("#password").type("secret123");
|
|
||||||
cy.get("#password").should("have.attr", "type", "password");
|
|
||||||
|
|
||||||
// Click the eye icon to show password
|
|
||||||
cy.get('label[for="passwordt"] span.cursor-pointer').click();
|
|
||||||
cy.get("#password").should("have.attr", "type", "text");
|
|
||||||
|
|
||||||
// Click again to hide
|
|
||||||
cy.get('label[for="passwordt"] span.cursor-pointer').click();
|
|
||||||
cy.get("#password").should("have.attr", "type", "password");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Logout Flow", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows account menu when head icon is clicked", () => {
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
|
|
||||||
// Click the head icon to open account menu
|
|
||||||
cy.get("#acct_mgmt_button").click();
|
|
||||||
cy.get("#account_menu").should("be.visible");
|
|
||||||
cy.get("#greeting").should("contain", "Test Admin");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("account menu shows admin management link for admin user", () => {
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
|
|
||||||
cy.get("#acct_mgmt_button").click();
|
|
||||||
cy.get("#account_menu").should("be.visible");
|
|
||||||
// Admin user should see account management option
|
|
||||||
cy.get("#btn_acct_mgmt").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("account menu has logout button", () => {
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
|
|
||||||
cy.get("#acct_mgmt_button").click();
|
|
||||||
cy.get("#btn_logout_in_menu").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking My Account navigates to /my-account", () => {
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
|
|
||||||
cy.get("#acct_mgmt_button").click();
|
|
||||||
cy.get("#btn_mang_ur_acct").click();
|
|
||||||
cy.url().should("include", "/my-account");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Account Management navigates to /account-admin", () => {
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
|
|
||||||
cy.get("#acct_mgmt_button").click();
|
|
||||||
cy.get("#btn_acct_mgmt").click();
|
|
||||||
cy.url().should("include", "/account-admin");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logout redirects to login page", () => {
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
|
|
||||||
cy.get("#acct_mgmt_button").click();
|
|
||||||
cy.get("#btn_logout_in_menu").click();
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("My Account Page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/my-account");
|
|
||||||
cy.wait("@getUserDetail");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays user name heading", () => {
|
|
||||||
cy.get("#general_acct_info_user_name").should("exist");
|
|
||||||
cy.get("#general_acct_info_user_name").should("contain", "Test Admin");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Admin badge for admin user", () => {
|
|
||||||
cy.contains("Admin").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows visit count info", () => {
|
|
||||||
cy.get("#general_account_visit_info").should("exist");
|
|
||||||
cy.get("#general_account_visit_info").should("contain", "Total visits");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays account username (read-only)", () => {
|
|
||||||
cy.contains("Test Admin").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Edit button for name field", () => {
|
|
||||||
cy.contains("button", "Edit").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Edit shows input field and Save/Cancel buttons", () => {
|
|
||||||
cy.contains("button", "Edit").first().click();
|
|
||||||
cy.get("#input_name_field").should("exist");
|
|
||||||
cy.contains("button", "Save").should("exist");
|
|
||||||
cy.contains("button", "Cancel").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Cancel reverts name field to read-only", () => {
|
|
||||||
cy.contains("button", "Edit").first().click();
|
|
||||||
cy.get("#input_name_field").should("exist");
|
|
||||||
cy.contains("button", "Cancel").click();
|
|
||||||
cy.get("#input_name_field").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Reset button for password field", () => {
|
|
||||||
cy.contains("button", "Reset").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Reset shows password input and Save/Cancel", () => {
|
|
||||||
cy.contains("button", "Reset").click();
|
|
||||||
cy.get('input[type="password"]').should("exist");
|
|
||||||
cy.contains("button", "Save").should("exist");
|
|
||||||
cy.contains("button", "Cancel").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Cancel on password field hides the input", () => {
|
|
||||||
cy.contains("button", "Reset").click();
|
|
||||||
cy.get('input[type="password"]').should("exist");
|
|
||||||
// The Cancel button for password is the second one
|
|
||||||
cy.get(".cancel-btn").click();
|
|
||||||
cy.get('input[type="password"]').should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows Session section", () => {
|
|
||||||
cy.contains("Session").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures, setupApiIntercepts } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Navigation and Routing", () => {
|
|
||||||
it("redirects / to /files when logged in", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/");
|
|
||||||
cy.url().should("include", "/files");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows 404 page for unknown routes", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/nonexistent-page");
|
|
||||||
cy.contains("404").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("navbar shows correct view name", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
cy.get("#nav_bar").should("exist");
|
|
||||||
cy.get("#nav_bar h2").should("contain", "FILES");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("navbar shows back arrow on non-files pages", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/discover/log/1/map");
|
|
||||||
// Back arrow should be visible on discover pages
|
|
||||||
cy.get("#backPage").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("navbar tabs are clickable on discover page", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/discover/log/1/map");
|
|
||||||
// Discover navbar should show MAP, CONFORMANCE, PERFORMANCE tabs
|
|
||||||
cy.contains(".nav-item", "MAP").should("exist");
|
|
||||||
cy.contains(".nav-item", "CONFORMANCE").should("exist");
|
|
||||||
cy.contains(".nav-item", "PERFORMANCE").should("exist");
|
|
||||||
|
|
||||||
// Click CONFORMANCE tab
|
|
||||||
cy.contains(".nav-item", "CONFORMANCE").click();
|
|
||||||
cy.url().should("include", "/conformance");
|
|
||||||
|
|
||||||
// Click PERFORMANCE tab
|
|
||||||
cy.contains(".nav-item", "PERFORMANCE").click();
|
|
||||||
cy.url().should("include", "/performance");
|
|
||||||
|
|
||||||
// Click MAP tab to go back
|
|
||||||
cy.contains(".nav-item", "MAP").click();
|
|
||||||
cy.url().should("include", "/map");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("login page is accessible at /login", () => {
|
|
||||||
setupApiIntercepts();
|
|
||||||
cy.visit("/login");
|
|
||||||
cy.get("h2").should("contain", "LOGIN");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("404 Not Found Page", () => {
|
|
||||||
it("displays 404 page for non-existent route", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/this-page-does-not-exist");
|
|
||||||
cy.contains("404").should("be.visible");
|
|
||||||
cy.contains("The page you are looking for does not exist.").should(
|
|
||||||
"be.visible",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has a link back to Files page", () => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/some/random/path");
|
|
||||||
cy.contains("a", "Go to Files")
|
|
||||||
.should("be.visible")
|
|
||||||
.should("have.attr", "href", "/files");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays 404 for unauthenticated user on invalid route", () => {
|
|
||||||
cy.visit("/not-a-real-page");
|
|
||||||
cy.url().then((url) => {
|
|
||||||
if (url.includes("/login")) {
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
} else {
|
|
||||||
cy.contains("404").should("be.visible");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/06/03
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Discover page navigation tabs", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Double-clicking a log file enters the MAP page.", () => {
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").dblclick();
|
|
||||||
cy.url().should("include", "map");
|
|
||||||
// MAP tab should exist in the navbar
|
|
||||||
cy.contains(".nav-item", "MAP").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Clicking CONFORMANCE tab switches active page.", () => {
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").dblclick();
|
|
||||||
cy.url().should("include", "map");
|
|
||||||
cy.contains(".nav-item", "CONFORMANCE").click();
|
|
||||||
cy.url().should("include", "conformance");
|
|
||||||
cy.contains(".nav-item", "CONFORMANCE").should("have.class", "active");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Clicking PERFORMANCE tab switches active page.", () => {
|
|
||||||
cy.contains("td.fileName", "sample-process.xes").dblclick();
|
|
||||||
cy.url().should("include", "map");
|
|
||||||
cy.contains(".nav-item", "PERFORMANCE").click();
|
|
||||||
cy.url().should("include", "performance");
|
|
||||||
cy.contains(".nav-item", "PERFORMANCE").should("have.class", "active");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/06/11
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { setupApiIntercepts } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("Paste URL login redirect", () => {
|
|
||||||
it("After login with return-to param, redirects to the remembered page", () => {
|
|
||||||
setupApiIntercepts();
|
|
||||||
|
|
||||||
// Visit login page with a return-to query param (base64-encoded URL)
|
|
||||||
const targetUrl =
|
|
||||||
"http://localhost:4173/discover/conformance/log/1/conformance";
|
|
||||||
const encodedUrl = btoa(targetUrl);
|
|
||||||
cy.visit(`/login?return-to=${encodedUrl}`);
|
|
||||||
|
|
||||||
// Fill in login form
|
|
||||||
cy.get("#account").type("testadmin");
|
|
||||||
cy.get("#password").type("password123");
|
|
||||||
cy.get("form").submit();
|
|
||||||
cy.wait("@postToken");
|
|
||||||
|
|
||||||
// After login, the app should attempt to redirect to the return-to URL.
|
|
||||||
// Since window.location.href is used (not router.push), we verify the
|
|
||||||
// login form disappears and the token cookie is set.
|
|
||||||
cy.getCookie("luciaToken").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Login without return-to param redirects to /files", () => {
|
|
||||||
setupApiIntercepts();
|
|
||||||
cy.visit("/login");
|
|
||||||
|
|
||||||
cy.get("#account").type("testadmin");
|
|
||||||
cy.get("#password").type("password123");
|
|
||||||
cy.get("form").submit();
|
|
||||||
cy.wait("@postToken");
|
|
||||||
|
|
||||||
cy.url().should("include", "/files");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Unauthenticated user cannot access inner pages", () => {
|
|
||||||
setupApiIntercepts();
|
|
||||||
// Override my-account to return 401 (simulate logged-out state)
|
|
||||||
cy.intercept("GET", "/api/my-account", {
|
|
||||||
statusCode: 401,
|
|
||||||
body: { detail: "Not authenticated" },
|
|
||||||
}).as("getMyAccountUnauth");
|
|
||||||
|
|
||||||
cy.visit("/files");
|
|
||||||
|
|
||||||
// Should be redirected to login page
|
|
||||||
cy.url().should("include", "/login");
|
|
||||||
cy.get("#account").should("exist");
|
|
||||||
cy.get("#password").should("exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
import { loginWithFixtures } from "../support/intercept";
|
|
||||||
|
|
||||||
describe("SweetAlert2 Modals", () => {
|
|
||||||
describe("File Context Menu - Rename", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("right-click on table row shows context menu with Rename", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Rename").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("right-click context menu shows Download option", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Download").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("right-click context menu shows Delete option", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Delete").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Rename opens SweetAlert rename dialog", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Rename").click();
|
|
||||||
// SweetAlert popup should appear with RENAME title
|
|
||||||
cy.get(".swal2-popup").should("be.visible");
|
|
||||||
cy.get(".swal2-title").should("contain", "RENAME");
|
|
||||||
cy.get(".swal2-input").should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rename dialog has pre-filled file name", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Rename").click();
|
|
||||||
cy.get(".swal2-input").should("not.have.value", "");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rename dialog can be cancelled", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Rename").click();
|
|
||||||
cy.get(".swal2-popup").should("be.visible");
|
|
||||||
cy.get(".swal2-cancel").click();
|
|
||||||
cy.get(".swal2-popup").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking Delete opens SweetAlert delete confirmation", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Delete").click();
|
|
||||||
// SweetAlert popup should appear with CONFIRM DELETION
|
|
||||||
cy.get(".swal2-popup").should("be.visible");
|
|
||||||
cy.get(".swal2-title").should("contain", "CONFIRM DELETION");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete confirmation shows file name", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Delete").click();
|
|
||||||
cy.get(".swal2-popup").should("be.visible");
|
|
||||||
cy.get(".swal2-html-container").should("contain", "delete");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete confirmation can be cancelled", () => {
|
|
||||||
cy.get("table tbody tr").first().rightclick();
|
|
||||||
cy.contains("Delete").click();
|
|
||||||
cy.get(".swal2-popup").should("be.visible");
|
|
||||||
cy.get(".swal2-cancel").click();
|
|
||||||
cy.get(".swal2-popup").should("not.exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("File Context Menu on Grid View", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/files");
|
|
||||||
cy.wait("@getFiles");
|
|
||||||
// Switch to grid view
|
|
||||||
cy.get("svg").parent("li.cursor-pointer").last().click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("right-click on grid card shows context menu", () => {
|
|
||||||
cy.get("li[title]").first().rightclick();
|
|
||||||
cy.contains("Rename").should("be.visible");
|
|
||||||
cy.contains("Delete").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("grid card rename opens SweetAlert dialog", () => {
|
|
||||||
cy.get("li[title]").first().rightclick();
|
|
||||||
cy.contains("Rename").click();
|
|
||||||
cy.get(".swal2-popup").should("be.visible");
|
|
||||||
cy.get(".swal2-title").should("contain", "RENAME");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Account Delete Confirmation", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
loginWithFixtures();
|
|
||||||
cy.visit("/account-admin");
|
|
||||||
cy.wait("@getUsers");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete confirmation Yes button triggers delete API", () => {
|
|
||||||
cy.get(".delete-account").first().click();
|
|
||||||
cy.get("#modal_container").should("be.visible");
|
|
||||||
cy.get("#sure_to_delete_acct_btn").click();
|
|
||||||
cy.wait("@deleteUser");
|
|
||||||
// Modal should close after deletion
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
{
|
|
||||||
"time": {
|
|
||||||
"avg_cycle_time": {
|
|
||||||
"primary": [
|
|
||||||
{ "date": "2022-01-15", "value": 500000 },
|
|
||||||
{ "date": "2022-06-15", "value": 600000 },
|
|
||||||
{ "date": "2022-12-15", "value": 550000 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "date": "2022-01-15", "value": 480000 },
|
|
||||||
{ "date": "2022-06-15", "value": 520000 },
|
|
||||||
{ "date": "2022-12-15", "value": 510000 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"avg_cycle_efficiency": {
|
|
||||||
"primary": [
|
|
||||||
{ "label": "File A", "value": 0.75 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "label": "File B", "value": 0.68 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"avg_process_time": {
|
|
||||||
"primary": [
|
|
||||||
{ "date": "2022-01-15", "value": 300000 },
|
|
||||||
{ "date": "2022-06-15", "value": 350000 },
|
|
||||||
{ "date": "2022-12-15", "value": 320000 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "date": "2022-01-15", "value": 280000 },
|
|
||||||
{ "date": "2022-06-15", "value": 310000 },
|
|
||||||
{ "date": "2022-12-15", "value": 290000 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"avg_process_time_by_task": {
|
|
||||||
"primary": [
|
|
||||||
{ "label": ["Activity", "A"], "value": 120000 },
|
|
||||||
{ "label": ["Activity", "B"], "value": 80000 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "label": ["Activity", "A"], "value": 110000 },
|
|
||||||
{ "label": ["Activity", "B"], "value": 95000 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"avg_waiting_time": {
|
|
||||||
"primary": [
|
|
||||||
{ "date": "2022-01-15", "value": 200000 },
|
|
||||||
{ "date": "2022-06-15", "value": 250000 },
|
|
||||||
{ "date": "2022-12-15", "value": 230000 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "date": "2022-01-15", "value": 200000 },
|
|
||||||
{ "date": "2022-06-15", "value": 210000 },
|
|
||||||
{ "date": "2022-12-15", "value": 220000 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"avg_waiting_time_by_edge": {
|
|
||||||
"primary": [
|
|
||||||
{ "label": ["A", "B"], "value": 150000 },
|
|
||||||
{ "label": ["B", "C"], "value": 100000 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "label": ["A", "B"], "value": 140000 },
|
|
||||||
{ "label": ["B", "C"], "value": 110000 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"freq": {
|
|
||||||
"cases": {
|
|
||||||
"primary": [
|
|
||||||
{ "count": 100 },
|
|
||||||
{ "count": 120 },
|
|
||||||
{ "count": 110 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "count": 95 },
|
|
||||||
{ "count": 105 },
|
|
||||||
{ "count": 100 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cases_by_task": {
|
|
||||||
"primary": [
|
|
||||||
{ "label": ["Activity", "A"], "value": 200 },
|
|
||||||
{ "label": ["Activity", "B"], "value": 150 }
|
|
||||||
],
|
|
||||||
"secondary": [
|
|
||||||
{ "label": ["Activity", "A"], "value": 180 },
|
|
||||||
{ "label": ["Activity", "B"], "value": 160 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,249 +0,0 @@
|
|||||||
{
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "a",
|
|
||||||
"occurrences": 187,
|
|
||||||
"occurrence_ratio": 0.10048361096184846,
|
|
||||||
"cases": 184,
|
|
||||||
"case_ratio": 0.7301587301587301
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "d",
|
|
||||||
"occurrences": 241,
|
|
||||||
"occurrence_ratio": 0.12950026867275657,
|
|
||||||
"cases": 241,
|
|
||||||
"case_ratio": 0.9563492063492064
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "e",
|
|
||||||
"occurrences": 249,
|
|
||||||
"occurrence_ratio": 0.1337990327780763,
|
|
||||||
"cases": 249,
|
|
||||||
"case_ratio": 0.9880952380952381
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "f",
|
|
||||||
"occurrences": 249,
|
|
||||||
"occurrence_ratio": 0.1337990327780763,
|
|
||||||
"cases": 249,
|
|
||||||
"case_ratio": 0.9880952380952381
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "g",
|
|
||||||
"occurrences": 253,
|
|
||||||
"occurrence_ratio": 0.13594841483073616,
|
|
||||||
"cases": 250,
|
|
||||||
"case_ratio": 0.9920634920634921
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "h",
|
|
||||||
"occurrences": 163,
|
|
||||||
"occurrence_ratio": 0.08758731864588931,
|
|
||||||
"cases": 163,
|
|
||||||
"case_ratio": 0.6468253968253969
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "i",
|
|
||||||
"occurrences": 185,
|
|
||||||
"occurrence_ratio": 0.09940891993551854,
|
|
||||||
"cases": 175,
|
|
||||||
"case_ratio": 0.6944444444444444
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "l",
|
|
||||||
"occurrences": 182,
|
|
||||||
"occurrence_ratio": 0.09779688339602365,
|
|
||||||
"cases": 182,
|
|
||||||
"case_ratio": 0.7222222222222222
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "c",
|
|
||||||
"occurrences": 48,
|
|
||||||
"occurrence_ratio": 0.025792584631918324,
|
|
||||||
"cases": 48,
|
|
||||||
"case_ratio": 0.19047619047619047
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "k",
|
|
||||||
"occurrences": 48,
|
|
||||||
"occurrence_ratio": 0.025792584631918324,
|
|
||||||
"cases": 48,
|
|
||||||
"case_ratio": 0.19047619047619047
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "b",
|
|
||||||
"occurrences": 34,
|
|
||||||
"occurrence_ratio": 0.018269747447608814,
|
|
||||||
"cases": 32,
|
|
||||||
"case_ratio": 0.12698412698412698
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "j",
|
|
||||||
"occurrences": 22,
|
|
||||||
"occurrence_ratio": 0.011821601289629231,
|
|
||||||
"cases": 22,
|
|
||||||
"case_ratio": 0.0873015873015873
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"label": "a",
|
|
||||||
"occurrences": 175,
|
|
||||||
"occurrence_ratio": 0.6944444444444444,
|
|
||||||
"sinks": [
|
|
||||||
"k",
|
|
||||||
"l"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "c",
|
|
||||||
"occurrences": 45,
|
|
||||||
"occurrence_ratio": 0.17857142857142858,
|
|
||||||
"sinks": [
|
|
||||||
"k"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "b",
|
|
||||||
"occurrences": 32,
|
|
||||||
"occurrence_ratio": 0.12698412698412698,
|
|
||||||
"sinks": [
|
|
||||||
"k",
|
|
||||||
"j",
|
|
||||||
"l"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sinks": [
|
|
||||||
{
|
|
||||||
"label": "l",
|
|
||||||
"occurrences": 182,
|
|
||||||
"occurrence_ratio": 0.7222222222222222,
|
|
||||||
"sources": [
|
|
||||||
"a",
|
|
||||||
"b"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "k",
|
|
||||||
"occurrences": 48,
|
|
||||||
"occurrence_ratio": 0.19047619047619047,
|
|
||||||
"sources": [
|
|
||||||
"a",
|
|
||||||
"b",
|
|
||||||
"c"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "j",
|
|
||||||
"occurrences": 22,
|
|
||||||
"occurrence_ratio": 0.0873015873015873,
|
|
||||||
"sources": [
|
|
||||||
"b"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeframe": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "2022-01-21T03:21:48",
|
|
||||||
"y": 347
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-02-26T08:13:24",
|
|
||||||
"y": 426
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-04-03T13:05:00",
|
|
||||||
"y": 394
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-05-09T17:56:36",
|
|
||||||
"y": 375
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-06-14T22:48:12",
|
|
||||||
"y": 431
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-07-21T03:39:48",
|
|
||||||
"y": 393
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-08-26T08:31:24",
|
|
||||||
"y": 284
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-10-01T13:23:00",
|
|
||||||
"y": 359
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-11-06T18:14:36",
|
|
||||||
"y": 386
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-12-12T23:06:12",
|
|
||||||
"y": 327
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"min": "2022-01-03T00:56:00",
|
|
||||||
"max": "2022-12-31T01:32:00"
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0,
|
|
||||||
"max": 431
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"traces": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"count": 95
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"count": 74
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"count": 45
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"count": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"count": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attrs": []
|
|
||||||
}
|
|
||||||
@@ -1,928 +0,0 @@
|
|||||||
{
|
|
||||||
"time": {
|
|
||||||
"avg_cycle_time": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "2022-01-21T03:21:48",
|
|
||||||
"y": 980220.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-02-26T08:13:24",
|
|
||||||
"y": 1000376.129032
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-04-03T13:05:00",
|
|
||||||
"y": 911990.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-05-09T17:56:36",
|
|
||||||
"y": 1041860.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-06-14T22:48:12",
|
|
||||||
"y": 985415.625
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-07-21T03:39:48",
|
|
||||||
"y": 938079.130435
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-08-26T08:31:24",
|
|
||||||
"y": 1074680.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-10-01T13:23:00",
|
|
||||||
"y": 1061848.695652
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-11-06T18:14:36",
|
|
||||||
"y": 970119.230769
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-12-12T23:06:12",
|
|
||||||
"y": 1060703.076923
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"min": "2022-01-03T00:56:00",
|
|
||||||
"max": "2022-12-31T01:32:00"
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 1074680.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"avg_cycle_efficiency": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "2022-01-21T03:21:48",
|
|
||||||
"y": 0.9527980523449506
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-02-26T08:13:24",
|
|
||||||
"y": 0.9516493513262202
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-04-03T13:05:00",
|
|
||||||
"y": 0.9475330648076836
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-05-09T17:56:36",
|
|
||||||
"y": 0.9537265449333607
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-06-14T22:48:12",
|
|
||||||
"y": 0.9528919667258132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-07-21T03:39:48",
|
|
||||||
"y": 0.9489804015433904
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-08-26T08:31:24",
|
|
||||||
"y": 0.9538748758272698
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-10-01T13:23:00",
|
|
||||||
"y": 0.9548679615433759
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-11-06T18:14:36",
|
|
||||||
"y": 0.9469965631092006
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-12-12T23:06:12",
|
|
||||||
"y": 0.9505469198562757
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"min": "2022-01-03T00:56:00",
|
|
||||||
"max": "2022-12-31T01:32:00"
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 1.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"avg_process_time": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "2022-01-21T03:21:48",
|
|
||||||
"y": 937067.368421
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-02-26T08:13:24",
|
|
||||||
"y": 953767.741935
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-04-03T13:05:00",
|
|
||||||
"y": 865780.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-05-09T17:56:36",
|
|
||||||
"y": 994600.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-06-14T22:48:12",
|
|
||||||
"y": 939795.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-07-21T03:39:48",
|
|
||||||
"y": 890947.826087
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-08-26T08:31:24",
|
|
||||||
"y": 1026345.714286
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-10-01T13:23:00",
|
|
||||||
"y": 1016363.478261
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-11-06T18:14:36",
|
|
||||||
"y": 923626.153846
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-12-12T23:06:12",
|
|
||||||
"y": 1011540.0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"min": "2022-01-03T00:56:00",
|
|
||||||
"max": "2022-12-31T01:32:00"
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 1026345.714286
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"avg_process_time_by_task": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "a",
|
|
||||||
"y": 131147.486631
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "b",
|
|
||||||
"y": 136627.058824
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "c",
|
|
||||||
"y": 133261.25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "d",
|
|
||||||
"y": 132697.095436
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "e",
|
|
||||||
"y": 124442.891566
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "f",
|
|
||||||
"y": 127175.180723
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "g",
|
|
||||||
"y": 127627.826087
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "h",
|
|
||||||
"y": 128163.680982
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "i",
|
|
||||||
"y": 125588.756757
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "j",
|
|
||||||
"y": 101290.909091
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "k",
|
|
||||||
"y": 142543.75
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "l",
|
|
||||||
"y": 138070.879121
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"labels": [
|
|
||||||
"a",
|
|
||||||
"b",
|
|
||||||
"c",
|
|
||||||
"d",
|
|
||||||
"e",
|
|
||||||
"f",
|
|
||||||
"g",
|
|
||||||
"h",
|
|
||||||
"i",
|
|
||||||
"j",
|
|
||||||
"k",
|
|
||||||
"l"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 142543.75
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"avg_waiting_time": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "2022-01-21T03:21:48",
|
|
||||||
"y": 43152.631579
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-02-26T08:13:24",
|
|
||||||
"y": 46608.387097
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-04-03T13:05:00",
|
|
||||||
"y": 46210.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-05-09T17:56:36",
|
|
||||||
"y": 47260.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-06-14T22:48:12",
|
|
||||||
"y": 45620.625
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-07-21T03:39:48",
|
|
||||||
"y": 47131.304348
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-08-26T08:31:24",
|
|
||||||
"y": 48334.285714
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-10-01T13:23:00",
|
|
||||||
"y": 45485.217391
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-11-06T18:14:36",
|
|
||||||
"y": 46493.076923
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-12-12T23:06:12",
|
|
||||||
"y": 49163.076923
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"min": "2022-01-03T00:56:00",
|
|
||||||
"max": "2022-12-31T01:32:00"
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 49163.076923
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"avg_waiting_time_by_edge": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"a",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
"y": 6420.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"a",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
"y": 7506.352941
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"a",
|
|
||||||
"f"
|
|
||||||
],
|
|
||||||
"y": 5940.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"a",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 5175.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"a",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
"y": 6260.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"b",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
"y": 6840.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"b",
|
|
||||||
"b"
|
|
||||||
],
|
|
||||||
"y": 3540.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"b",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 7273.636364
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"b",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
"y": 6288.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"c",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 11460.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"c",
|
|
||||||
"h"
|
|
||||||
],
|
|
||||||
"y": 6821.73913
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"c",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
"y": 13500.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"d",
|
|
||||||
"c"
|
|
||||||
],
|
|
||||||
"y": 11760.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"d",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
"y": 7166.694915
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"d",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 8080.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"d",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
"y": 3600.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"e",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
"y": 7260.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"e",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
"y": 6780.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"e",
|
|
||||||
"f"
|
|
||||||
],
|
|
||||||
"y": 7288.474576
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"e",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 14040.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"e",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
"y": 13620.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"e",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
"y": 3780.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"f",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
"y": 10140.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"f",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
"y": 3940.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"f",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 6983.271028
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"f",
|
|
||||||
"j"
|
|
||||||
],
|
|
||||||
"y": 8170.909091
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"f",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
"y": 6667.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"c"
|
|
||||||
],
|
|
||||||
"y": 2400.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
"y": 11880.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"f"
|
|
||||||
],
|
|
||||||
"y": 5302.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 11400.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"h"
|
|
||||||
],
|
|
||||||
"y": 7592.820513
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
"y": 7140.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
"y": 8116.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"g",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
"y": 7457.368421
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"h",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
"y": 7288.888889
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"h",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
"y": 6960.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
"y": 8910.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"b"
|
|
||||||
],
|
|
||||||
"y": 5880.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"c"
|
|
||||||
],
|
|
||||||
"y": 5460.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
"y": 7710.447761
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
"y": 9153.333333
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
"y": 8640.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
"y": 6324.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
"y": 3240.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": [
|
|
||||||
"i",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
"y": 7188.75
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"labels": [
|
|
||||||
[
|
|
||||||
"a",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"a",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"a",
|
|
||||||
"f"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"a",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"a",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"b",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"b",
|
|
||||||
"b"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"b",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"b",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"c",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"c",
|
|
||||||
"h"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"c",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"d",
|
|
||||||
"c"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"d",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"d",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"d",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"e",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"e",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"e",
|
|
||||||
"f"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"e",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"e",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"e",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"f",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"f",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"f",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"f",
|
|
||||||
"j"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"f",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"c"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"f"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"h"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"g",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"h",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"h",
|
|
||||||
"l"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"a"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"b"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"c"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"d"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"e"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"g"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"i"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"k"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"i",
|
|
||||||
"l"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 14040.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"freq": {
|
|
||||||
"cases": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "2022-01-21T03:21:48",
|
|
||||||
"y": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-02-26T08:13:24",
|
|
||||||
"y": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-04-03T13:05:00",
|
|
||||||
"y": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-05-09T17:56:36",
|
|
||||||
"y": 26
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-06-14T22:48:12",
|
|
||||||
"y": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-07-21T03:39:48",
|
|
||||||
"y": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-08-26T08:31:24",
|
|
||||||
"y": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-10-01T13:23:00",
|
|
||||||
"y": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-11-06T18:14:36",
|
|
||||||
"y": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "2022-12-12T23:06:12",
|
|
||||||
"y": 17
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"min": "2022-01-03T00:56:00",
|
|
||||||
"max": "2022-12-31T01:32:00"
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0,
|
|
||||||
"max": 30
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cases_by_task": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"x": "a",
|
|
||||||
"y": 184
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "b",
|
|
||||||
"y": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "c",
|
|
||||||
"y": 48
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "d",
|
|
||||||
"y": 241
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "e",
|
|
||||||
"y": 249
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "f",
|
|
||||||
"y": 249
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "g",
|
|
||||||
"y": 250
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "h",
|
|
||||||
"y": 163
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "i",
|
|
||||||
"y": 175
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "j",
|
|
||||||
"y": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "k",
|
|
||||||
"y": 48
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": "l",
|
|
||||||
"y": 182
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x_axis": {
|
|
||||||
"labels": [
|
|
||||||
"a",
|
|
||||||
"b",
|
|
||||||
"c",
|
|
||||||
"d",
|
|
||||||
"e",
|
|
||||||
"f",
|
|
||||||
"g",
|
|
||||||
"h",
|
|
||||||
"i",
|
|
||||||
"j",
|
|
||||||
"k",
|
|
||||||
"l"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"y_axis": {
|
|
||||||
"min": 0,
|
|
||||||
"max": 250
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "testadmin",
|
|
||||||
"name": "Test Admin",
|
|
||||||
"is_admin": true,
|
|
||||||
"is_active": true,
|
|
||||||
"is_sso": false,
|
|
||||||
"has_data": true,
|
|
||||||
"roles": [
|
|
||||||
{ "code": "admin", "name": "Administrator" }
|
|
||||||
],
|
|
||||||
"detail": {
|
|
||||||
"visits": 42
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
時間,案號,居住區域,學區,事件名稱,事件序號,狀態 ,時段,溫度,數量,未知,完成,預期時間
|
|
||||||
2022/05/13 09:25:21,案一,63,富山,事件甲,事件一,Start,早上,95,27,,TRUE,2022/5/14 09:25:21
|
|
||||||
2022/05/13 09:25:21,案一,,,事件甲,事件一,Complete,中午,135,442,,false,2022/5/14 09:25:21
|
|
||||||
2022/05/13 09:30:01,案一,,仁德,事件乙,事件四,Start,中午,110.6,-6, ,,2022/5/14 09:30:01
|
|
||||||
2022/05/13 09:30:01,案一,,,事件乙,事件四,Complete,晚上,-65,4,,true ,2022/5/14 09:30:01
|
|
||||||
|
Binary file not shown.
|
@@ -1 +0,0 @@
|
|||||||
timestamp,case id,name,instance,status
|
|
||||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,9 +0,0 @@
|
|||||||
timestamp,case id,name,status
|
|
||||||
2022/05/13 09:25:21,c1,a,1,start
|
|
||||||
2022/05/13 09:25:21,c1,a,1,complete
|
|
||||||
2022/05/13 09:30:01,c1,b,2,start
|
|
||||||
2022/05/13 09:30:01,c1,b,2,complete
|
|
||||||
2022/05/13 09:48:33,c2,a,6,start
|
|
||||||
2022/05/13 09:48:33,c2,a,6,complete
|
|
||||||
2022/05/13 09:54:27,c2,c,7,start
|
|
||||||
2022/05/13 09:54:27,c2,c,7,complete
|
|
||||||
|
@@ -1,30 +0,0 @@
|
|||||||
CaseID,Activity,Timestamp,Status,Activity_Instance
|
|
||||||
CID_1,檢傷,2023-01-12 15:32:31,start,1
|
|
||||||
CID_1,檢傷,2023-01-12 15:32:31,complete,1
|
|
||||||
CID_1,第一次醫囑,2023-01-12 15:49:01,start,2
|
|
||||||
CID_1,第一次醫囑,2023-01-12 15:49:01,complete,2
|
|
||||||
CID_1,出院,2023-01-13 03:32:00,start,3
|
|
||||||
CID_1,出院,2023-01-13 03:32:00,complete,3
|
|
||||||
CID_2,檢傷,2023-07-26 08:44:17,start,4
|
|
||||||
CID_2,檢傷,2023-07-26 08:44:17,complete,4
|
|
||||||
CID_2,第一次醫囑,2023-07-26 08:48:05,start,5
|
|
||||||
CID_2,第一次醫囑,2023-07-26 08:48:05,complete,5
|
|
||||||
CID_2,出院,2023-07-26 17:00:49,start,6
|
|
||||||
CID_2,出院,2023-07-26 17:00:49,complete,6
|
|
||||||
CID_3,檢傷,2023-11-27 06:20:48,start,7
|
|
||||||
CID_3,檢傷,2023-11-27 06:20:48,complete,7
|
|
||||||
CID_3,第一次醫囑,2023-11-27 06:40:30,start,8
|
|
||||||
CID_3,第一次醫囑,2023-11-27 06:40:30,complete,8
|
|
||||||
CID_3,出院,2023-11-27 07:21:53,start,9
|
|
||||||
CID_3,出院,2023-11-27 07:21:53,complete,9
|
|
||||||
CID_4,檢傷,2023-11-01 01:20:19,start,10
|
|
||||||
CID_4,檢傷,2023-11-01 01:20:19,complete,10
|
|
||||||
CID_4,第一次醫囑,2023-11-01 01:34:54,start,11
|
|
||||||
CID_4,第一次醫囑,2023-11-01 01:34:54,complete,11
|
|
||||||
CID_4,住院,2023-11-02 06:36:36,start,12
|
|
||||||
CID_4,住院,2023-11-10 02:35:39,complete,12
|
|
||||||
CID_4,出院,2023-11-10 02:35:39,start,13
|
|
||||||
CID_4,出院,2023-11-10 02:35:39,complete,13
|
|
||||||
CID_5,檢傷,2023-07-13 02:49:36,start,14
|
|
||||||
CID_5,檢傷,2023-07-13 02:49:36,complete,14
|
|
||||||
CID_5,第一次醫囑,2023-07-13 03:07:01,start,15
|
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
timestamp,case id,name,instance,status
|
|
||||||
2022/05/13 09:25:21,c1,a,1,start
|
|
||||||
2022/05/13 09:25:21,c1,a,1,
|
|
||||||
2022/05/13 09:30:01,c1,b,2,begin
|
|
||||||
2022/05/13 09:30:01,c1,b,,complete
|
|
||||||
2022/05/13 09:48:33,,,6,start
|
|
||||||
2022;05;13 09;48;33,c2,a,6,complete
|
|
||||||
2022/05/13 09:54:27,c2,c,7,start
|
|
||||||
,c2,c,7,complete
|
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"username": " test ",
|
|
||||||
"password": " test "
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2023-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// chiayin.kuo@dsp.im (chiayin), 2023/01/31
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/06
|
|
||||||
|
|
||||||
// ***********************************************
|
|
||||||
// This example commands.js shows you how to
|
|
||||||
// create various custom commands and overwrite
|
|
||||||
// existing commands.
|
|
||||||
//
|
|
||||||
// For more comprehensive examples of custom
|
|
||||||
// commands please read more here:
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
// ***********************************************
|
|
||||||
// -- This is a parent command --
|
|
||||||
import "@4tw/cypress-drag-drop";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets authentication cookies to simulate a logged-in user.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
Cypress.Commands.add("login", () => {
|
|
||||||
cy.setCookie("luciaToken", "fake-access-token-for-testing");
|
|
||||||
cy.setCookie("isLuciaLoggedIn", "true");
|
|
||||||
});
|
|
||||||
// Usage: cy.login()
|
|
||||||
// -- This is a child command --
|
|
||||||
// Click a blank area to close an opened modal: cy.closePopup()
|
|
||||||
/**
|
|
||||||
* Closes the active popup by clicking the top-left area of the page.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
Cypress.Commands.add("closePopup", () => {
|
|
||||||
// Trigger a forced click to close modal overlays consistently.
|
|
||||||
cy.get("body").click({ position: "topLeft" });
|
|
||||||
});
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2023-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// chiayin.kuo@dsp.im (chiayin), 2023/01/31
|
|
||||||
|
|
||||||
// ***********************************************************
|
|
||||||
// This example support/index.js is processed and
|
|
||||||
// loaded automatically before your test files.
|
|
||||||
//
|
|
||||||
// This is a great place to put global configuration and
|
|
||||||
// behavior that modifies Cypress.
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off
|
|
||||||
// automatically serving support files with the
|
|
||||||
// 'supportFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/configuration
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
|
||||||
import "./commands";
|
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
|
||||||
// require('./commands')
|
|
||||||
require("cypress-xpath"); // Enables xpath helpers used in pointer event checks.
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
// The Lucia project.
|
|
||||||
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
||||||
// Authors:
|
|
||||||
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up cy.intercept for all API endpoints using fixture files.
|
|
||||||
* Call setupApiIntercepts() in beforeEach to mock the entire backend.
|
|
||||||
*/
|
|
||||||
export function setupApiIntercepts() {
|
|
||||||
// Auth
|
|
||||||
cy.intercept("POST", "/api/oauth/token", {
|
|
||||||
fixture: "api/token.json",
|
|
||||||
}).as("postToken");
|
|
||||||
|
|
||||||
// User account
|
|
||||||
cy.intercept("GET", "/api/my-account", {
|
|
||||||
fixture: "api/my-account.json",
|
|
||||||
}).as("getMyAccount");
|
|
||||||
|
|
||||||
cy.intercept("PUT", "/api/my-account", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("putMyAccount");
|
|
||||||
|
|
||||||
// Files
|
|
||||||
cy.intercept("GET", "/api/files", {
|
|
||||||
fixture: "api/files.json",
|
|
||||||
}).as("getFiles");
|
|
||||||
|
|
||||||
// Users (account management)
|
|
||||||
cy.intercept("GET", "/api/users", {
|
|
||||||
fixture: "api/users.json",
|
|
||||||
}).as("getUsers");
|
|
||||||
|
|
||||||
cy.intercept("POST", "/api/users", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("postUser");
|
|
||||||
|
|
||||||
cy.intercept("DELETE", "/api/users/*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("deleteUser");
|
|
||||||
|
|
||||||
cy.intercept("PUT", "/api/users/*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("putUser");
|
|
||||||
|
|
||||||
// User detail (GET /api/users/:username)
|
|
||||||
cy.intercept("GET", "/api/users/*", {
|
|
||||||
fixture: "api/user-detail.json",
|
|
||||||
}).as("getUserDetail");
|
|
||||||
|
|
||||||
// User roles
|
|
||||||
cy.intercept("PUT", "/api/users/*/roles/*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("putUserRole");
|
|
||||||
|
|
||||||
cy.intercept("DELETE", "/api/users/*/roles/*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("deleteUserRole");
|
|
||||||
|
|
||||||
// Filter detail (for fetchFunnel when entering filter from Files)
|
|
||||||
cy.intercept("GET", /\/api\/filters\/\d+$/, {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { rules: [], log: { id: 1 }, name: "filtered-sample" },
|
|
||||||
}).as("getFilterDetail");
|
|
||||||
|
|
||||||
// Discover (map data)
|
|
||||||
cy.intercept("GET", "/api/logs/*/discover", {
|
|
||||||
fixture: "api/discover.json",
|
|
||||||
}).as("getDiscover");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filters/*/discover", {
|
|
||||||
fixture: "api/discover.json",
|
|
||||||
}).as("getFilterDiscover");
|
|
||||||
|
|
||||||
// Performance
|
|
||||||
cy.intercept("GET", "/api/logs/*/performance", {
|
|
||||||
fixture: "api/performance.json",
|
|
||||||
}).as("getPerformance");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filters/*/performance", {
|
|
||||||
fixture: "api/performance.json",
|
|
||||||
}).as("getFilterPerformance");
|
|
||||||
|
|
||||||
// Traces
|
|
||||||
cy.intercept("GET", "/api/logs/*/traces", {
|
|
||||||
fixture: "api/traces.json",
|
|
||||||
}).as("getTraces");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filters/*/traces", {
|
|
||||||
fixture: "api/traces.json",
|
|
||||||
}).as("getFilterTraces");
|
|
||||||
|
|
||||||
// Trace detail (must be after traces list intercepts)
|
|
||||||
cy.intercept("GET", /\/api\/logs\/.*\/traces\/\d+/, {
|
|
||||||
fixture: "api/trace-detail.json",
|
|
||||||
}).as("getTraceDetail");
|
|
||||||
|
|
||||||
cy.intercept("GET", /\/api\/filters\/.*\/traces\/\d+/, {
|
|
||||||
fixture: "api/trace-detail.json",
|
|
||||||
}).as("getFilterTraceDetail");
|
|
||||||
|
|
||||||
// Temp filters
|
|
||||||
cy.intercept("GET", "/api/temp-filters/*/discover", {
|
|
||||||
fixture: "api/discover.json",
|
|
||||||
}).as("getTempFilterDiscover");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/temp-filters/*/traces", {
|
|
||||||
fixture: "api/traces.json",
|
|
||||||
}).as("getTempFilterTraces");
|
|
||||||
|
|
||||||
// Filter params
|
|
||||||
cy.intercept("GET", "/api/filters/params*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: {},
|
|
||||||
}).as("getFilterParams");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filters/has-result*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: false,
|
|
||||||
}).as("getFilterHasResult");
|
|
||||||
|
|
||||||
// Conformance check params
|
|
||||||
cy.intercept("GET", "/api/log-checks/params*", {
|
|
||||||
fixture: "api/filter-params.json",
|
|
||||||
}).as("getLogCheckParams");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filter-checks/params*", {
|
|
||||||
fixture: "api/filter-params.json",
|
|
||||||
}).as("getFilterCheckParams");
|
|
||||||
|
|
||||||
// Compare dashboard
|
|
||||||
cy.intercept("GET", /\/api\/compare\?datasets=/, {
|
|
||||||
fixture: "api/compare.json",
|
|
||||||
}).as("getCompare");
|
|
||||||
|
|
||||||
// Dependents (for delete confirmation)
|
|
||||||
cy.intercept("GET", "/api/logs/*/dependents", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: [],
|
|
||||||
}).as("getLogDependents");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filters/*/dependents", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: [],
|
|
||||||
}).as("getFilterDependents");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/log-checks/*/dependents", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: [],
|
|
||||||
}).as("getLogCheckDependents");
|
|
||||||
|
|
||||||
cy.intercept("GET", "/api/filter-checks/*/dependents", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: [],
|
|
||||||
}).as("getFilterCheckDependents");
|
|
||||||
|
|
||||||
// Rename
|
|
||||||
cy.intercept("PUT", "/api/logs/*/rename", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("renameLog");
|
|
||||||
|
|
||||||
cy.intercept("PUT", "/api/filters/*/rename", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("renameFilter");
|
|
||||||
|
|
||||||
// Deletion
|
|
||||||
cy.intercept("DELETE", "/api/deletion/*", {
|
|
||||||
statusCode: 200,
|
|
||||||
body: { success: true },
|
|
||||||
}).as("deleteDeletion");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the luciaToken cookie and isLuciaLoggedIn cookie to simulate
|
|
||||||
* a logged-in state, then sets up all API intercepts.
|
|
||||||
*/
|
|
||||||
export function loginWithFixtures() {
|
|
||||||
setupApiIntercepts();
|
|
||||||
cy.setCookie("luciaToken", "fake-access-token-for-testing");
|
|
||||||
cy.setCookie("isLuciaLoggedIn", "true");
|
|
||||||
}
|
|
||||||
Generated
+851
-3232
File diff suppressed because it is too large
Load Diff
+8
-10
@@ -5,13 +5,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"build:e2e": "VITE_MSW=true vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"coverage": "vitest run --coverage",
|
"coverage": "vitest run --coverage",
|
||||||
"cy:run": "cypress run",
|
"test:unit": "vitest run",
|
||||||
"test:unit": "vitest --environment jsdom",
|
"test:e2e": "playwright test --config tests/e2e/playwright.config.ts",
|
||||||
"test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'",
|
"test:e2e:ui": "playwright test --config tests/e2e/playwright.config.ts --ui",
|
||||||
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
|
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs",
|
||||||
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix",
|
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix",
|
||||||
"docs": "typedoc"
|
"docs": "typedoc"
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
"cytoscape-dagre": "^2.5.0",
|
"cytoscape-dagre": "^2.5.0",
|
||||||
"cytoscape-fcose": "^2.2.0",
|
"cytoscape-fcose": "^2.2.0",
|
||||||
"cytoscape-popper": "^4.0.1",
|
"cytoscape-popper": "^4.0.1",
|
||||||
"cytoscape-spread": "^3.0.0",
|
|
||||||
"decimal.js": "^10.6.0",
|
"decimal.js": "^10.6.0",
|
||||||
"i18next": "^25.8.14",
|
"i18next": "^25.8.14",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
@@ -49,8 +48,8 @@
|
|||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@4tw/cypress-drag-drop": "^2.3.1",
|
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@playwright/test": "^1.58.2",
|
||||||
"@types/cytoscape": "^3.21.9",
|
"@types/cytoscape": "^3.21.9",
|
||||||
"@types/cytoscape-dagre": "^2.3.4",
|
"@types/cytoscape-dagre": "^2.3.4",
|
||||||
"@types/cytoscape-popper": "^2.0.4",
|
"@types/cytoscape-popper": "^2.0.4",
|
||||||
@@ -58,17 +57,16 @@
|
|||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"ajv": "^8.18.0",
|
||||||
|
"ajv-formats": "^3.0.1",
|
||||||
"chartjs-plugin-dragdata": "^2.3.1",
|
"chartjs-plugin-dragdata": "^2.3.1",
|
||||||
"cypress": "^15.11.0",
|
|
||||||
"cypress-xpath": "^2.0.1",
|
|
||||||
"eslint": "^10.0.2",
|
"eslint": "^10.0.2",
|
||||||
"eslint-plugin-cypress": "^6.1.0",
|
|
||||||
"eslint-plugin-vue": "^10.8.0",
|
"eslint-plugin-vue": "^10.8.0",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
|
"msw": "^2.12.14",
|
||||||
"postcss": "^8.5.8",
|
"postcss": "^8.5.8",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"start-server-and-test": "^2.1.5",
|
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typedoc": "^0.28.17",
|
"typedoc": "^0.28.17",
|
||||||
|
|||||||
@@ -0,0 +1,349 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Service Worker.
|
||||||
|
* @see https://github.com/mswjs/msw
|
||||||
|
* - Please do NOT modify this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PACKAGE_VERSION = '2.12.14'
|
||||||
|
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
|
||||||
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
||||||
|
const activeClientIds = new Set()
|
||||||
|
|
||||||
|
addEventListener('install', function () {
|
||||||
|
self.skipWaiting()
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('activate', function (event) {
|
||||||
|
event.waitUntil(self.clients.claim())
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('message', async function (event) {
|
||||||
|
const clientId = Reflect.get(event.source || {}, 'id')
|
||||||
|
|
||||||
|
if (!clientId || !self.clients) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = await self.clients.get(clientId)
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClients = await self.clients.matchAll({
|
||||||
|
type: 'window',
|
||||||
|
})
|
||||||
|
|
||||||
|
switch (event.data) {
|
||||||
|
case 'KEEPALIVE_REQUEST': {
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'KEEPALIVE_RESPONSE',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'INTEGRITY_CHECK_REQUEST': {
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'INTEGRITY_CHECK_RESPONSE',
|
||||||
|
payload: {
|
||||||
|
packageVersion: PACKAGE_VERSION,
|
||||||
|
checksum: INTEGRITY_CHECKSUM,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOCK_ACTIVATE': {
|
||||||
|
activeClientIds.add(clientId)
|
||||||
|
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'MOCKING_ENABLED',
|
||||||
|
payload: {
|
||||||
|
client: {
|
||||||
|
id: client.id,
|
||||||
|
frameType: client.frameType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'CLIENT_CLOSED': {
|
||||||
|
activeClientIds.delete(clientId)
|
||||||
|
|
||||||
|
const remainingClients = allClients.filter((client) => {
|
||||||
|
return client.id !== clientId
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unregister itself when there are no more clients
|
||||||
|
if (remainingClients.length === 0) {
|
||||||
|
self.registration.unregister()
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('fetch', function (event) {
|
||||||
|
const requestInterceptedAt = Date.now()
|
||||||
|
|
||||||
|
// Bypass navigation requests.
|
||||||
|
if (event.request.mode === 'navigate') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening the DevTools triggers the "only-if-cached" request
|
||||||
|
// that cannot be handled by the worker. Bypass such requests.
|
||||||
|
if (
|
||||||
|
event.request.cache === 'only-if-cached' &&
|
||||||
|
event.request.mode !== 'same-origin'
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass all requests when there are no active clients.
|
||||||
|
// Prevents the self-unregistered worked from handling requests
|
||||||
|
// after it's been terminated (still remains active until the next reload).
|
||||||
|
if (activeClientIds.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = crypto.randomUUID()
|
||||||
|
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FetchEvent} event
|
||||||
|
* @param {string} requestId
|
||||||
|
* @param {number} requestInterceptedAt
|
||||||
|
*/
|
||||||
|
async function handleRequest(event, requestId, requestInterceptedAt) {
|
||||||
|
const client = await resolveMainClient(event)
|
||||||
|
const requestCloneForEvents = event.request.clone()
|
||||||
|
const response = await getResponse(
|
||||||
|
event,
|
||||||
|
client,
|
||||||
|
requestId,
|
||||||
|
requestInterceptedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send back the response clone for the "response:*" life-cycle events.
|
||||||
|
// Ensure MSW is active and ready to handle the message, otherwise
|
||||||
|
// this message will pend indefinitely.
|
||||||
|
if (client && activeClientIds.has(client.id)) {
|
||||||
|
const serializedRequest = await serializeRequest(requestCloneForEvents)
|
||||||
|
|
||||||
|
// Clone the response so both the client and the library could consume it.
|
||||||
|
const responseClone = response.clone()
|
||||||
|
|
||||||
|
sendToClient(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
type: 'RESPONSE',
|
||||||
|
payload: {
|
||||||
|
isMockedResponse: IS_MOCKED_RESPONSE in response,
|
||||||
|
request: {
|
||||||
|
id: requestId,
|
||||||
|
...serializedRequest,
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
type: responseClone.type,
|
||||||
|
status: responseClone.status,
|
||||||
|
statusText: responseClone.statusText,
|
||||||
|
headers: Object.fromEntries(responseClone.headers.entries()),
|
||||||
|
body: responseClone.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the main client for the given event.
|
||||||
|
* Client that issues a request doesn't necessarily equal the client
|
||||||
|
* that registered the worker. It's with the latter the worker should
|
||||||
|
* communicate with during the response resolving phase.
|
||||||
|
* @param {FetchEvent} event
|
||||||
|
* @returns {Promise<Client | undefined>}
|
||||||
|
*/
|
||||||
|
async function resolveMainClient(event) {
|
||||||
|
const client = await self.clients.get(event.clientId)
|
||||||
|
|
||||||
|
if (activeClientIds.has(event.clientId)) {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client?.frameType === 'top-level') {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClients = await self.clients.matchAll({
|
||||||
|
type: 'window',
|
||||||
|
})
|
||||||
|
|
||||||
|
return allClients
|
||||||
|
.filter((client) => {
|
||||||
|
// Get only those clients that are currently visible.
|
||||||
|
return client.visibilityState === 'visible'
|
||||||
|
})
|
||||||
|
.find((client) => {
|
||||||
|
// Find the client ID that's recorded in the
|
||||||
|
// set of clients that have registered the worker.
|
||||||
|
return activeClientIds.has(client.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FetchEvent} event
|
||||||
|
* @param {Client | undefined} client
|
||||||
|
* @param {string} requestId
|
||||||
|
* @param {number} requestInterceptedAt
|
||||||
|
* @returns {Promise<Response>}
|
||||||
|
*/
|
||||||
|
async function getResponse(event, client, requestId, requestInterceptedAt) {
|
||||||
|
// Clone the request because it might've been already used
|
||||||
|
// (i.e. its body has been read and sent to the client).
|
||||||
|
const requestClone = event.request.clone()
|
||||||
|
|
||||||
|
function passthrough() {
|
||||||
|
// Cast the request headers to a new Headers instance
|
||||||
|
// so the headers can be manipulated with.
|
||||||
|
const headers = new Headers(requestClone.headers)
|
||||||
|
|
||||||
|
// Remove the "accept" header value that marked this request as passthrough.
|
||||||
|
// This prevents request alteration and also keeps it compliant with the
|
||||||
|
// user-defined CORS policies.
|
||||||
|
const acceptHeader = headers.get('accept')
|
||||||
|
if (acceptHeader) {
|
||||||
|
const values = acceptHeader.split(',').map((value) => value.trim())
|
||||||
|
const filteredValues = values.filter(
|
||||||
|
(value) => value !== 'msw/passthrough',
|
||||||
|
)
|
||||||
|
|
||||||
|
if (filteredValues.length > 0) {
|
||||||
|
headers.set('accept', filteredValues.join(', '))
|
||||||
|
} else {
|
||||||
|
headers.delete('accept')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(requestClone, { headers })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass mocking when the client is not active.
|
||||||
|
if (!client) {
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass initial page load requests (i.e. static assets).
|
||||||
|
// The absence of the immediate/parent client in the map of the active clients
|
||||||
|
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||||
|
// and is not ready to handle requests.
|
||||||
|
if (!activeClientIds.has(client.id)) {
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the client that a request has been intercepted.
|
||||||
|
const serializedRequest = await serializeRequest(event.request)
|
||||||
|
const clientMessage = await sendToClient(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
type: 'REQUEST',
|
||||||
|
payload: {
|
||||||
|
id: requestId,
|
||||||
|
interceptedAt: requestInterceptedAt,
|
||||||
|
...serializedRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[serializedRequest.body],
|
||||||
|
)
|
||||||
|
|
||||||
|
switch (clientMessage.type) {
|
||||||
|
case 'MOCK_RESPONSE': {
|
||||||
|
return respondWithMock(clientMessage.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'PASSTHROUGH': {
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Client} client
|
||||||
|
* @param {any} message
|
||||||
|
* @param {Array<Transferable>} transferrables
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
function sendToClient(client, message, transferrables = []) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const channel = new MessageChannel()
|
||||||
|
|
||||||
|
channel.port1.onmessage = (event) => {
|
||||||
|
if (event.data && event.data.error) {
|
||||||
|
return reject(event.data.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(event.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.postMessage(message, [
|
||||||
|
channel.port2,
|
||||||
|
...transferrables.filter(Boolean),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Response} response
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
function respondWithMock(response) {
|
||||||
|
// Setting response status code to 0 is a no-op.
|
||||||
|
// However, when responding with a "Response.error()", the produced Response
|
||||||
|
// instance will have status code set to 0. Since it's not possible to create
|
||||||
|
// a Response instance with status code 0, handle that use-case separately.
|
||||||
|
if (response.status === 0) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockedResponse = new Response(response.body, response)
|
||||||
|
|
||||||
|
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
|
||||||
|
value: true,
|
||||||
|
enumerable: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return mockedResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
|
async function serializeRequest(request) {
|
||||||
|
return {
|
||||||
|
url: request.url,
|
||||||
|
mode: request.mode,
|
||||||
|
method: request.method,
|
||||||
|
headers: Object.fromEntries(request.headers.entries()),
|
||||||
|
cache: request.cache,
|
||||||
|
credentials: request.credentials,
|
||||||
|
destination: request.destination,
|
||||||
|
integrity: request.integrity,
|
||||||
|
redirect: request.redirect,
|
||||||
|
referrer: request.referrer,
|
||||||
|
referrerPolicy: request.referrerPolicy,
|
||||||
|
body: await request.arrayBuffer(),
|
||||||
|
keepalive: request.keepalive,
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-1
@@ -98,4 +98,18 @@ app.component("ContextMenu", ContextMenu);
|
|||||||
app.component("Draggable", draggable); // Drag and drop
|
app.component("Draggable", draggable); // Drag and drop
|
||||||
app.directive("tooltip", Tooltip);
|
app.directive("tooltip", Tooltip);
|
||||||
|
|
||||||
app.mount("#app");
|
/**
|
||||||
|
* Starts the MSW service worker when VITE_MSW is set.
|
||||||
|
* Used for E2E testing with mock API responses.
|
||||||
|
*/
|
||||||
|
async function enableMocking() {
|
||||||
|
if (import.meta.env.VITE_MSW !== "true") return;
|
||||||
|
const { http, HttpResponse } = await import("msw");
|
||||||
|
const { worker } = await import("./mocks/browser.js");
|
||||||
|
await worker.start({ onUnhandledRequest: "bypass" });
|
||||||
|
// Expose on window so Cypress can add per-test overrides
|
||||||
|
window.__mswWorker__ = worker;
|
||||||
|
window.__msw__ = { http, HttpResponse };
|
||||||
|
}
|
||||||
|
|
||||||
|
enableMocking().then(() => app.mount("#app"));
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* MSW worker for browser (Cypress E2E).
|
||||||
|
* @module mocks/browser
|
||||||
|
*/
|
||||||
|
import { setupWorker } from "msw/browser";
|
||||||
|
import { handlers } from "./handlers/index.js";
|
||||||
|
|
||||||
|
export const worker = setupWorker(...handlers);
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,249 @@
|
|||||||
|
{
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "a",
|
||||||
|
"occurrences": 187,
|
||||||
|
"occurrence_ratio": 0.10048361096184846,
|
||||||
|
"cases": 184,
|
||||||
|
"case_ratio": 0.7301587301587301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "d",
|
||||||
|
"occurrences": 241,
|
||||||
|
"occurrence_ratio": 0.12950026867275657,
|
||||||
|
"cases": 241,
|
||||||
|
"case_ratio": 0.9563492063492064
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "e",
|
||||||
|
"occurrences": 249,
|
||||||
|
"occurrence_ratio": 0.1337990327780763,
|
||||||
|
"cases": 249,
|
||||||
|
"case_ratio": 0.9880952380952381
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "f",
|
||||||
|
"occurrences": 249,
|
||||||
|
"occurrence_ratio": 0.1337990327780763,
|
||||||
|
"cases": 249,
|
||||||
|
"case_ratio": 0.9880952380952381
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "g",
|
||||||
|
"occurrences": 253,
|
||||||
|
"occurrence_ratio": 0.13594841483073616,
|
||||||
|
"cases": 250,
|
||||||
|
"case_ratio": 0.9920634920634921
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "h",
|
||||||
|
"occurrences": 163,
|
||||||
|
"occurrence_ratio": 0.08758731864588931,
|
||||||
|
"cases": 163,
|
||||||
|
"case_ratio": 0.6468253968253969
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "i",
|
||||||
|
"occurrences": 185,
|
||||||
|
"occurrence_ratio": 0.09940891993551854,
|
||||||
|
"cases": 175,
|
||||||
|
"case_ratio": 0.6944444444444444
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "l",
|
||||||
|
"occurrences": 182,
|
||||||
|
"occurrence_ratio": 0.09779688339602365,
|
||||||
|
"cases": 182,
|
||||||
|
"case_ratio": 0.7222222222222222
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "c",
|
||||||
|
"occurrences": 48,
|
||||||
|
"occurrence_ratio": 0.025792584631918324,
|
||||||
|
"cases": 48,
|
||||||
|
"case_ratio": 0.19047619047619047
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "k",
|
||||||
|
"occurrences": 48,
|
||||||
|
"occurrence_ratio": 0.025792584631918324,
|
||||||
|
"cases": 48,
|
||||||
|
"case_ratio": 0.19047619047619047
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "b",
|
||||||
|
"occurrences": 34,
|
||||||
|
"occurrence_ratio": 0.018269747447608814,
|
||||||
|
"cases": 32,
|
||||||
|
"case_ratio": 0.12698412698412698
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "j",
|
||||||
|
"occurrences": 22,
|
||||||
|
"occurrence_ratio": 0.011821601289629231,
|
||||||
|
"cases": 22,
|
||||||
|
"case_ratio": 0.0873015873015873
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"label": "a",
|
||||||
|
"occurrences": 175,
|
||||||
|
"occurrence_ratio": 0.6944444444444444,
|
||||||
|
"sinks": [
|
||||||
|
"k",
|
||||||
|
"l"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "c",
|
||||||
|
"occurrences": 45,
|
||||||
|
"occurrence_ratio": 0.17857142857142858,
|
||||||
|
"sinks": [
|
||||||
|
"k"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "b",
|
||||||
|
"occurrences": 32,
|
||||||
|
"occurrence_ratio": 0.12698412698412698,
|
||||||
|
"sinks": [
|
||||||
|
"k",
|
||||||
|
"j",
|
||||||
|
"l"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sinks": [
|
||||||
|
{
|
||||||
|
"label": "l",
|
||||||
|
"occurrences": 182,
|
||||||
|
"occurrence_ratio": 0.7222222222222222,
|
||||||
|
"sources": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "k",
|
||||||
|
"occurrences": 48,
|
||||||
|
"occurrence_ratio": 0.19047619047619047,
|
||||||
|
"sources": [
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "j",
|
||||||
|
"occurrences": 22,
|
||||||
|
"occurrence_ratio": 0.0873015873015873,
|
||||||
|
"sources": [
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeframe": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "2022-01-21T03:21:48Z",
|
||||||
|
"y": 347
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-02-26T08:13:24Z",
|
||||||
|
"y": 426
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-04-03T13:05:00Z",
|
||||||
|
"y": 394
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-05-09T17:56:36Z",
|
||||||
|
"y": 375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-06-14T22:48:12Z",
|
||||||
|
"y": 431
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-07-21T03:39:48Z",
|
||||||
|
"y": 393
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-08-26T08:31:24Z",
|
||||||
|
"y": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-10-01T13:23:00Z",
|
||||||
|
"y": 359
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-11-06T18:14:36Z",
|
||||||
|
"y": 386
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-12-12T23:06:12Z",
|
||||||
|
"y": 327
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"min": "2022-01-03T00:56:00Z",
|
||||||
|
"max": "2022-12-31T01:32:00Z"
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 431
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"traces": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"count": 95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"count": 74
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"count": 45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"count": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"count": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"count": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attrs": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,928 @@
|
|||||||
|
{
|
||||||
|
"time": {
|
||||||
|
"avg_cycle_time": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "2022-01-21T03:21:48Z",
|
||||||
|
"y": "PT272H17M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-02-26T08:13:24Z",
|
||||||
|
"y": "PT277H52M56.13S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-04-03T13:05:00Z",
|
||||||
|
"y": "PT253H19M50.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-05-09T17:56:36Z",
|
||||||
|
"y": "PT289H24M20.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-06-14T22:48:12Z",
|
||||||
|
"y": "PT273H43M35.62S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-07-21T03:39:48Z",
|
||||||
|
"y": "PT260H34M39.13S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-08-26T08:31:24Z",
|
||||||
|
"y": "PT298H31M20.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-10-01T13:23:00Z",
|
||||||
|
"y": "PT294H57M28.7S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-11-06T18:14:36Z",
|
||||||
|
"y": "PT269H28M39.23S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-12-12T23:06:12Z",
|
||||||
|
"y": "PT294H38M23.08S"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"min": "2022-01-03T00:56:00Z",
|
||||||
|
"max": "2022-12-31T01:32:00Z"
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": "PT0S",
|
||||||
|
"max": "PT298H31M20.0S"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avg_cycle_efficiency": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "2022-01-21T03:21:48Z",
|
||||||
|
"y": 0.9527980523449506
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-02-26T08:13:24Z",
|
||||||
|
"y": 0.9516493513262202
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-04-03T13:05:00Z",
|
||||||
|
"y": 0.9475330648076836
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-05-09T17:56:36Z",
|
||||||
|
"y": 0.9537265449333607
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-06-14T22:48:12Z",
|
||||||
|
"y": 0.9528919667258132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-07-21T03:39:48Z",
|
||||||
|
"y": 0.9489804015433904
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-08-26T08:31:24Z",
|
||||||
|
"y": 0.9538748758272698
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-10-01T13:23:00Z",
|
||||||
|
"y": 0.9548679615433759
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-11-06T18:14:36Z",
|
||||||
|
"y": 0.9469965631092006
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-12-12T23:06:12Z",
|
||||||
|
"y": 0.9505469198562757
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"min": "2022-01-03T00:56:00Z",
|
||||||
|
"max": "2022-12-31T01:32:00Z"
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avg_process_time": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "2022-01-21T03:21:48Z",
|
||||||
|
"y": "PT260H17M47.37S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-02-26T08:13:24Z",
|
||||||
|
"y": "PT264H56M7.74S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-04-03T13:05:00Z",
|
||||||
|
"y": "PT240H29M40.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-05-09T17:56:36Z",
|
||||||
|
"y": "PT276H16M40.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-06-14T22:48:12Z",
|
||||||
|
"y": "PT261H3M15.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-07-21T03:39:48Z",
|
||||||
|
"y": "PT247H29M7.83S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-08-26T08:31:24Z",
|
||||||
|
"y": "PT285H5M45.71S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-10-01T13:23:00Z",
|
||||||
|
"y": "PT282H19M23.48S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-11-06T18:14:36Z",
|
||||||
|
"y": "PT256H33M46.15S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-12-12T23:06:12Z",
|
||||||
|
"y": "PT280H59M"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"min": "2022-01-03T00:56:00Z",
|
||||||
|
"max": "2022-12-31T01:32:00Z"
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": "PT0S",
|
||||||
|
"max": "PT285H5M45.71S"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avg_process_time_by_task": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "aT00:00:00Z",
|
||||||
|
"y": "PT36H25M47.49S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "bT00:00:00Z",
|
||||||
|
"y": "PT37H57M7.06S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "cT00:00:00Z",
|
||||||
|
"y": "PT37H1M1.25S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "dT00:00:00Z",
|
||||||
|
"y": "PT36H51M37.1S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "eT00:00:00Z",
|
||||||
|
"y": "PT34H34M2.89S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "fT00:00:00Z",
|
||||||
|
"y": "PT35H19M35.18S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "gT00:00:00Z",
|
||||||
|
"y": "PT35H27M7.83S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "hT00:00:00Z",
|
||||||
|
"y": "PT35H36M3.68S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "iT00:00:00Z",
|
||||||
|
"y": "PT34H53M8.76S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "jT00:00:00Z",
|
||||||
|
"y": "PT28H8M10.91S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "kT00:00:00Z",
|
||||||
|
"y": "PT39H35M43.75S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "lT00:00:00Z",
|
||||||
|
"y": "PT38H21M10.88S"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"labels": [
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"d",
|
||||||
|
"e",
|
||||||
|
"f",
|
||||||
|
"g",
|
||||||
|
"h",
|
||||||
|
"i",
|
||||||
|
"j",
|
||||||
|
"k",
|
||||||
|
"l"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": "PT0S",
|
||||||
|
"max": "PT39H35M43.75S"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avg_waiting_time": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "2022-01-21T03:21:48Z",
|
||||||
|
"y": "PT11H59M12.63S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-02-26T08:13:24Z",
|
||||||
|
"y": "PT12H56M48.39S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-04-03T13:05:00Z",
|
||||||
|
"y": "PT12H50M10.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-05-09T17:56:36Z",
|
||||||
|
"y": "PT13H7M40.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-06-14T22:48:12Z",
|
||||||
|
"y": "PT12H40M20.62S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-07-21T03:39:48Z",
|
||||||
|
"y": "PT13H5M31.3S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-08-26T08:31:24Z",
|
||||||
|
"y": "PT13H25M34.29S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-10-01T13:23:00Z",
|
||||||
|
"y": "PT12H38M5.22S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-11-06T18:14:36Z",
|
||||||
|
"y": "PT12H54M53.08S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-12-12T23:06:12Z",
|
||||||
|
"y": "PT13H39M23.08S"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"min": "2022-01-03T00:56:00Z",
|
||||||
|
"max": "2022-12-31T01:32:00Z"
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": "PT0S",
|
||||||
|
"max": "PT13H39M23.08S"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avg_waiting_time_by_edge": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"a",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
"y": "PT1H47M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"a",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
"y": "PT2H5M6.35S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"a",
|
||||||
|
"f"
|
||||||
|
],
|
||||||
|
"y": "PT1H39M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"a",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT1H26M15.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"a",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"y": "PT1H44M20.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"b",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
"y": "PT1H54M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"b",
|
||||||
|
"b"
|
||||||
|
],
|
||||||
|
"y": "PT59M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"b",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT2H1M13.64S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"b",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"y": "PT1H44M48.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"c",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT3H11M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"c",
|
||||||
|
"h"
|
||||||
|
],
|
||||||
|
"y": "PT1H53M41.74S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"c",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
"y": "PT3H45M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"d",
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
"y": "PT3H16M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"d",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
"y": "PT1H59M26.69S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"d",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT2H14M40.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"d",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"y": "PT1H"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"e",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
"y": "PT2H1M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"e",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
"y": "PT1H53M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"e",
|
||||||
|
"f"
|
||||||
|
],
|
||||||
|
"y": "PT2H1M28.47S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"e",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT3H54M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"e",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
"y": "PT3H47M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"e",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
"y": "PT1H3M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"f",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
"y": "PT2H49M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"f",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
"y": "PT1H5M40.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"f",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT1H56M23.27S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"f",
|
||||||
|
"j"
|
||||||
|
],
|
||||||
|
"y": "PT2H16M10.91S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"f",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
"y": "PT1H51M7.5S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
"y": "PT40M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
"y": "PT3H18M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"f"
|
||||||
|
],
|
||||||
|
"y": "PT1H28M22.5S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT3H10M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"h"
|
||||||
|
],
|
||||||
|
"y": "PT2H6M32.82S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"y": "PT1H59M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
"y": "PT2H15M16.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"g",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
"y": "PT2H4M17.37S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"h",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"y": "PT2H1M28.89S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"h",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
"y": "PT1H56M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
"y": "PT2H28M30.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"b"
|
||||||
|
],
|
||||||
|
"y": "PT1H38M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
"y": "PT1H31M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
"y": "PT2H8M30.45S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
"y": "PT2H32M33.33S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
"y": "PT2H24M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"y": "PT1H45M24.0S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
"y": "PT54M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": [
|
||||||
|
"i",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
"y": "PT1H59M48.75S"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"labels": [
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"f"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"b",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"b",
|
||||||
|
"b"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"b",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"b",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"c",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"c",
|
||||||
|
"h"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"c",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"d",
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"d",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"d",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"d",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"e",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"e",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"e",
|
||||||
|
"f"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"e",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"e",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"e",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"f",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"f",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"f",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"f",
|
||||||
|
"j"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"f",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"f"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"h"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"g",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"h",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"h",
|
||||||
|
"l"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"b"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"c"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"d"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"e"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"g"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"i",
|
||||||
|
"l"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": "PT0S",
|
||||||
|
"max": "PT3H54M"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"freq": {
|
||||||
|
"cases": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "2022-01-21T03:21:48Z",
|
||||||
|
"y": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-02-26T08:13:24Z",
|
||||||
|
"y": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-04-03T13:05:00Z",
|
||||||
|
"y": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-05-09T17:56:36Z",
|
||||||
|
"y": 26
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-06-14T22:48:12Z",
|
||||||
|
"y": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-07-21T03:39:48Z",
|
||||||
|
"y": 27
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-08-26T08:31:24Z",
|
||||||
|
"y": 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-10-01T13:23:00Z",
|
||||||
|
"y": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-11-06T18:14:36Z",
|
||||||
|
"y": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "2022-12-12T23:06:12Z",
|
||||||
|
"y": 17
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"min": "2022-01-03T00:56:00Z",
|
||||||
|
"max": "2022-12-31T01:32:00Z"
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cases_by_task": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"x": "aT00:00:00Z",
|
||||||
|
"y": 184
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "bT00:00:00Z",
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "cT00:00:00Z",
|
||||||
|
"y": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "dT00:00:00Z",
|
||||||
|
"y": 241
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "eT00:00:00Z",
|
||||||
|
"y": 249
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "fT00:00:00Z",
|
||||||
|
"y": 249
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "gT00:00:00Z",
|
||||||
|
"y": 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "hT00:00:00Z",
|
||||||
|
"y": 163
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "iT00:00:00Z",
|
||||||
|
"y": 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "jT00:00:00Z",
|
||||||
|
"y": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "kT00:00:00Z",
|
||||||
|
"y": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "lT00:00:00Z",
|
||||||
|
"y": 182
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x_axis": {
|
||||||
|
"labels": [
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"d",
|
||||||
|
"e",
|
||||||
|
"f",
|
||||||
|
"g",
|
||||||
|
"h",
|
||||||
|
"i",
|
||||||
|
"j",
|
||||||
|
"k",
|
||||||
|
"l"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"y_axis": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,141 +12,141 @@
|
|||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"id": "H00564053",
|
"id": "H00564053",
|
||||||
"started_at": "2022-01-03T00:56:00",
|
"started_at": "2022-01-03T00:56:00Z",
|
||||||
"completed_at": "2022-01-12T02:29:00",
|
"completed_at": "2022-01-12T02:29:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00723931",
|
"id": "H00723931",
|
||||||
"started_at": "2022-01-04T17:51:00",
|
"started_at": "2022-01-04T17:51:00Z",
|
||||||
"completed_at": "2022-01-17T10:00:00",
|
"completed_at": "2022-01-17T10:00:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00542949",
|
"id": "H00542949",
|
||||||
"started_at": "2022-01-10T19:05:00",
|
"started_at": "2022-01-10T19:05:00Z",
|
||||||
"completed_at": "2022-01-28T09:38:00",
|
"completed_at": "2022-01-28T09:38:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00320575",
|
"id": "H00320575",
|
||||||
"started_at": "2022-01-12T21:35:00",
|
"started_at": "2022-01-12T21:35:00Z",
|
||||||
"completed_at": "2022-01-24T19:38:00",
|
"completed_at": "2022-01-24T19:38:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00565387",
|
"id": "H00565387",
|
||||||
"started_at": "2022-01-20T20:30:00",
|
"started_at": "2022-01-20T20:30:00Z",
|
||||||
"completed_at": "2022-02-06T10:57:00",
|
"completed_at": "2022-02-06T10:57:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00832338",
|
"id": "H00832338",
|
||||||
"started_at": "2022-01-29T15:00:00",
|
"started_at": "2022-01-29T15:00:00Z",
|
||||||
"completed_at": "2022-02-13T17:46:00",
|
"completed_at": "2022-02-13T17:46:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00525137",
|
"id": "H00525137",
|
||||||
"started_at": "2022-02-05T23:26:00",
|
"started_at": "2022-02-05T23:26:00Z",
|
||||||
"completed_at": "2022-02-14T19:47:00",
|
"completed_at": "2022-02-14T19:47:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00093124",
|
"id": "H00093124",
|
||||||
"started_at": "2022-02-09T16:56:00",
|
"started_at": "2022-02-09T16:56:00Z",
|
||||||
"completed_at": "2022-02-28T17:38:00",
|
"completed_at": "2022-02-28T17:38:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00657586",
|
"id": "H00657586",
|
||||||
"started_at": "2022-02-14T20:07:00",
|
"started_at": "2022-02-14T20:07:00Z",
|
||||||
"completed_at": "2022-02-28T15:21:00",
|
"completed_at": "2022-02-28T15:21:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00141668",
|
"id": "H00141668",
|
||||||
"started_at": "2022-02-17T13:57:00",
|
"started_at": "2022-02-17T13:57:00Z",
|
||||||
"completed_at": "2022-03-06T00:01:00",
|
"completed_at": "2022-03-06T00:01:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00493818",
|
"id": "H00493818",
|
||||||
"started_at": "2022-02-20T19:54:00",
|
"started_at": "2022-02-20T19:54:00Z",
|
||||||
"completed_at": "2022-03-05T05:06:00",
|
"completed_at": "2022-03-05T05:06:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00488827",
|
"id": "H00488827",
|
||||||
"started_at": "2022-02-21T00:38:00",
|
"started_at": "2022-02-21T00:38:00Z",
|
||||||
"completed_at": "2022-03-03T16:24:00",
|
"completed_at": "2022-03-03T16:24:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00874806",
|
"id": "H00874806",
|
||||||
"started_at": "2022-02-24T15:15:00",
|
"started_at": "2022-02-24T15:15:00Z",
|
||||||
"completed_at": "2022-03-12T01:12:00",
|
"completed_at": "2022-03-12T01:12:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00395448",
|
"id": "H00395448",
|
||||||
"started_at": "2022-02-26T03:35:00",
|
"started_at": "2022-02-26T03:35:00Z",
|
||||||
"completed_at": "2022-03-08T23:11:00",
|
"completed_at": "2022-03-08T23:11:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00414605",
|
"id": "H00414605",
|
||||||
"started_at": "2022-02-26T17:11:00",
|
"started_at": "2022-02-26T17:11:00Z",
|
||||||
"completed_at": "2022-03-10T08:50:00",
|
"completed_at": "2022-03-10T08:50:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00564269",
|
"id": "H00564269",
|
||||||
"started_at": "2022-03-04T01:18:00",
|
"started_at": "2022-03-04T01:18:00Z",
|
||||||
"completed_at": "2022-03-16T08:14:00",
|
"completed_at": "2022-03-16T08:14:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00729845",
|
"id": "H00729845",
|
||||||
"started_at": "2022-03-05T09:29:00",
|
"started_at": "2022-03-05T09:29:00Z",
|
||||||
"completed_at": "2022-03-17T15:25:00",
|
"completed_at": "2022-03-17T15:25:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00194115",
|
"id": "H00194115",
|
||||||
"started_at": "2022-03-09T18:58:00",
|
"started_at": "2022-03-09T18:58:00Z",
|
||||||
"completed_at": "2022-03-23T09:01:00",
|
"completed_at": "2022-03-23T09:01:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00517238",
|
"id": "H00517238",
|
||||||
"started_at": "2022-03-21T06:30:00",
|
"started_at": "2022-03-21T06:30:00Z",
|
||||||
"completed_at": "2022-04-05T05:27:00",
|
"completed_at": "2022-04-05T05:27:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "H00377237",
|
"id": "H00377237",
|
||||||
"started_at": "2022-03-24T12:06:00",
|
"started_at": "2022-03-24T12:06:00Z",
|
||||||
"completed_at": "2022-04-04T15:44:00",
|
"completed_at": "2022-04-04T15:44:00Z",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"facets": []
|
"facets": []
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"username": "testadmin",
|
||||||
|
"name": "Test Admin",
|
||||||
|
"is_active": true,
|
||||||
|
"is_sso": false,
|
||||||
|
"visits": 42,
|
||||||
|
"visited_at": "2025-06-12T09:00:00Z",
|
||||||
|
"created_at": "2024-01-01T00:00:00Z",
|
||||||
|
"created_by": { "username": "system", "name": "System" },
|
||||||
|
"updated_at": "2025-06-10T14:30:00Z",
|
||||||
|
"updated_by": { "username": "testadmin", "name": "Test Admin" },
|
||||||
|
"roles": [
|
||||||
|
{ "code": "admin", "name": "Administrator" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for current user account endpoints.
|
||||||
|
* @module mocks/handlers/account
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import myAccountData from "../fixtures/my-account.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const accountHandlers = [
|
||||||
|
http.get("/api/my-account", ({ request }) => {
|
||||||
|
captureRequest("GET", "/api/my-account");
|
||||||
|
return HttpResponse.json(myAccountData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.put("/api/my-account", async ({ request }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
captureRequest("PUT", "/api/my-account", body);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for authentication endpoints.
|
||||||
|
* @module mocks/handlers/auth
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import tokenData from "../fixtures/token.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const authHandlers = [
|
||||||
|
http.post("/api/oauth/token", async ({ request }) => {
|
||||||
|
const body = await request.text();
|
||||||
|
captureRequest("POST", "/api/oauth/token", body);
|
||||||
|
return HttpResponse.json(tokenData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for comparison dashboard endpoints.
|
||||||
|
* @module mocks/handlers/compare
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import compareData from "../fixtures/compare.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const compareHandlers = [
|
||||||
|
http.get("/api/compare", ({ request }) => {
|
||||||
|
captureRequest("GET", "/api/compare");
|
||||||
|
return HttpResponse.json(compareData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for conformance check parameter endpoints.
|
||||||
|
* @module mocks/handlers/conformance
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import filterParamsData from "../fixtures/filter-params.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const conformanceHandlers = [
|
||||||
|
http.get("/api/log-checks/params*", ({ request }) => {
|
||||||
|
captureRequest("GET", new URL(request.url).pathname);
|
||||||
|
return HttpResponse.json(filterParamsData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filter-checks/params*", ({ request }) => {
|
||||||
|
captureRequest("GET", new URL(request.url).pathname);
|
||||||
|
return HttpResponse.json(filterParamsData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for process discovery (map) endpoints.
|
||||||
|
* @module mocks/handlers/discover
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import discoverData from "../fixtures/discover.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const discoverHandlers = [
|
||||||
|
http.get("/api/logs/:id/discover", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/logs/${params.id}/discover`);
|
||||||
|
return HttpResponse.json(discoverData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filters/:id/discover", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filters/${params.id}/discover`);
|
||||||
|
return HttpResponse.json(discoverData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/temp-filters/:id/discover", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/temp-filters/${params.id}/discover`);
|
||||||
|
return HttpResponse.json(discoverData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for file management endpoints.
|
||||||
|
* @module mocks/handlers/files
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import filesData from "../fixtures/files.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const filesHandlers = [
|
||||||
|
http.get("/api/files", ({ request }) => {
|
||||||
|
captureRequest("GET", "/api/files");
|
||||||
|
return HttpResponse.json(filesData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Combined MSW request handlers for all API endpoints.
|
||||||
|
* @module mocks/handlers
|
||||||
|
*/
|
||||||
|
import { authHandlers } from "./auth.js";
|
||||||
|
import { accountHandlers } from "./account.js";
|
||||||
|
import { usersHandlers } from "./users.js";
|
||||||
|
import { filesHandlers } from "./files.js";
|
||||||
|
import { discoverHandlers } from "./discover.js";
|
||||||
|
import { performanceHandlers } from "./performance.js";
|
||||||
|
import { tracesHandlers } from "./traces.js";
|
||||||
|
import { conformanceHandlers } from "./conformance.js";
|
||||||
|
import { compareHandlers } from "./compare.js";
|
||||||
|
import { operationsHandlers } from "./operations.js";
|
||||||
|
|
||||||
|
export const handlers = [
|
||||||
|
...authHandlers,
|
||||||
|
...accountHandlers,
|
||||||
|
...usersHandlers,
|
||||||
|
...filesHandlers,
|
||||||
|
...discoverHandlers,
|
||||||
|
...performanceHandlers,
|
||||||
|
...tracesHandlers,
|
||||||
|
...conformanceHandlers,
|
||||||
|
...compareHandlers,
|
||||||
|
...operationsHandlers,
|
||||||
|
];
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for filter detail, filter params, dependents,
|
||||||
|
* rename, and deletion endpoints.
|
||||||
|
* @module mocks/handlers/operations
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const operationsHandlers = [
|
||||||
|
// Filter detail
|
||||||
|
http.get("/api/filters/:id", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filters/${params.id}`);
|
||||||
|
return HttpResponse.json({
|
||||||
|
rules: [],
|
||||||
|
log: { id: 1 },
|
||||||
|
name: "filtered-sample",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Filter params and has-result
|
||||||
|
http.get("/api/filters/params*", ({ request }) => {
|
||||||
|
captureRequest("GET", new URL(request.url).pathname);
|
||||||
|
return HttpResponse.json({});
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filters/has-result*", ({ request }) => {
|
||||||
|
captureRequest("GET", new URL(request.url).pathname);
|
||||||
|
return HttpResponse.json(false);
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Dependents
|
||||||
|
http.get("/api/logs/:id/dependents", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/logs/${params.id}/dependents`);
|
||||||
|
return HttpResponse.json([]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filters/:id/dependents", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filters/${params.id}/dependents`);
|
||||||
|
return HttpResponse.json([]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/log-checks/:id/dependents", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/log-checks/${params.id}/dependents`);
|
||||||
|
return HttpResponse.json([]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filter-checks/:id/dependents", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filter-checks/${params.id}/dependents`);
|
||||||
|
return HttpResponse.json([]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Rename
|
||||||
|
http.put("/api/logs/:id/rename", async ({ request, params }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
captureRequest("PUT", `/api/logs/${params.id}/rename`, body);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.put("/api/filters/:id/rename", async ({ request, params }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
captureRequest("PUT", `/api/filters/${params.id}/rename`, body);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Deletion
|
||||||
|
http.delete("/api/deletion/:id", ({ params }) => {
|
||||||
|
captureRequest("DELETE", `/api/deletion/${params.id}`);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for performance metrics endpoints.
|
||||||
|
* @module mocks/handlers/performance
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import performanceData from "../fixtures/performance.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const performanceHandlers = [
|
||||||
|
http.get("/api/logs/:id/performance", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/logs/${params.id}/performance`);
|
||||||
|
return HttpResponse.json(performanceData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filters/:id/performance", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filters/${params.id}/performance`);
|
||||||
|
return HttpResponse.json(performanceData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for trace list and detail endpoints.
|
||||||
|
* @module mocks/handlers/traces
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import tracesData from "../fixtures/traces.json";
|
||||||
|
import traceDetailData from "../fixtures/trace-detail.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const tracesHandlers = [
|
||||||
|
http.get("/api/logs/:logId/traces/:traceId", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/logs/${params.logId}/traces/${params.traceId}`);
|
||||||
|
return HttpResponse.json(traceDetailData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filters/:filterId/traces/:traceId", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filters/${params.filterId}/traces/${params.traceId}`);
|
||||||
|
return HttpResponse.json(traceDetailData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/logs/:id/traces", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/logs/${params.id}/traces`);
|
||||||
|
return HttpResponse.json(tracesData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/filters/:id/traces", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/filters/${params.id}/traces`);
|
||||||
|
return HttpResponse.json(tracesData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.get("/api/temp-filters/:id/traces", ({ params }) => {
|
||||||
|
captureRequest("GET", `/api/temp-filters/${params.id}/traces`);
|
||||||
|
return HttpResponse.json(tracesData);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* MSW handlers for user management (account admin) endpoints.
|
||||||
|
* @module mocks/handlers/users
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import usersData from "../fixtures/users.json";
|
||||||
|
import userDetailData from "../fixtures/user-detail.json";
|
||||||
|
import { captureRequest } from "../request-log.js";
|
||||||
|
|
||||||
|
export const usersHandlers = [
|
||||||
|
http.get("/api/users", ({ request }) => {
|
||||||
|
captureRequest("GET", "/api/users");
|
||||||
|
return HttpResponse.json(usersData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.post("/api/users", async ({ request }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
captureRequest("POST", "/api/users", body);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
|
||||||
|
// User roles (must be before /api/users/:username to match first)
|
||||||
|
http.put("/api/users/:username/roles/:role", async ({ request, params }) => {
|
||||||
|
captureRequest("PUT", `/api/users/${params.username}/roles/${params.role}`);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.delete("/api/users/:username/roles/:role", ({ request, params }) => {
|
||||||
|
captureRequest("DELETE", `/api/users/${params.username}/roles/${params.role}`);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
|
||||||
|
// User CRUD (after roles so roles match first)
|
||||||
|
http.get("/api/users/:username", ({ request, params }) => {
|
||||||
|
captureRequest("GET", `/api/users/${params.username}`);
|
||||||
|
return HttpResponse.json(userDetailData);
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.put("/api/users/:username", async ({ request, params }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
captureRequest("PUT", `/api/users/${params.username}`, body);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
|
||||||
|
http.delete("/api/users/:username", ({ request, params }) => {
|
||||||
|
captureRequest("DELETE", `/api/users/${params.username}`);
|
||||||
|
return HttpResponse.json({ success: true });
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* MSW server for Node.js (Vitest).
|
||||||
|
* @module mocks/node
|
||||||
|
*/
|
||||||
|
import { setupServer } from "msw/node";
|
||||||
|
import { handlers } from "./handlers/index.js";
|
||||||
|
|
||||||
|
export const server = setupServer(...handlers);
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Request logging utility for MSW handlers.
|
||||||
|
* Captures request details for test assertions,
|
||||||
|
* replacing vi.fn() call verification.
|
||||||
|
* @module mocks/request-log
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Array<{method: string, url: string, body: any}>} */
|
||||||
|
let log = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a request in the log. Called from MSW handlers.
|
||||||
|
* @param {string} method - HTTP method.
|
||||||
|
* @param {string} url - Request URL path.
|
||||||
|
* @param {any} [body] - Parsed request body.
|
||||||
|
*/
|
||||||
|
export function captureRequest(method, url, body = undefined) {
|
||||||
|
log.push({ method, url, body });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of all logged requests.
|
||||||
|
* @returns {Array<{method: string, url: string, body: any}>}
|
||||||
|
*/
|
||||||
|
export function getRequests() {
|
||||||
|
return [...log];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the request log. Call in afterEach.
|
||||||
|
*/
|
||||||
|
export function clearRequests() {
|
||||||
|
log = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds logged requests matching the given method and URL pattern.
|
||||||
|
* @param {string} method - HTTP method to match.
|
||||||
|
* @param {string|RegExp} urlPattern - URL string or regex to match.
|
||||||
|
* @returns {Array<{method: string, url: string, body: any}>}
|
||||||
|
*/
|
||||||
|
export function findRequest(method, urlPattern) {
|
||||||
|
return log.filter((r) => {
|
||||||
|
if (r.method !== method) return false;
|
||||||
|
if (typeof urlPattern === "string") return r.url === urlPattern;
|
||||||
|
return urlPattern.test(r.url);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import cytoscape from "cytoscape";
|
import cytoscape from "cytoscape";
|
||||||
import spread from "cytoscape-spread";
|
|
||||||
import dagre from "cytoscape-dagre";
|
import dagre from "cytoscape-dagre";
|
||||||
import fcose from "cytoscape-fcose";
|
import fcose from "cytoscape-fcose";
|
||||||
import cola from "cytoscape-cola";
|
import cola from "cytoscape-cola";
|
||||||
@@ -48,7 +47,6 @@ const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => {
|
|||||||
|
|
||||||
// Register layout algorithms
|
// Register layout algorithms
|
||||||
cytoscape.use(dagre);
|
cytoscape.use(dagre);
|
||||||
cytoscape.use(spread);
|
|
||||||
cytoscape.use(fcose);
|
cytoscape.use(fcose);
|
||||||
cytoscape.use(cola);
|
cytoscape.use(cola);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import apiError from '@/module/apiError';
|
|||||||
import { useLoginStore } from '@/stores/login';
|
import { useLoginStore } from '@/stores/login';
|
||||||
import { JUST_CREATE_ACCOUNT_HOT_DURATION_MINS } from '@/constants/constants';
|
import { JUST_CREATE_ACCOUNT_HOT_DURATION_MINS } from '@/constants/constants';
|
||||||
|
|
||||||
interface User {
|
export interface User {
|
||||||
username: string;
|
username: string;
|
||||||
detail: Record<string, any>;
|
detail: Record<string, any>;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -28,7 +28,7 @@ interface User {
|
|||||||
isDetailHovered?: boolean;
|
isDetailHovered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditDetail {
|
export interface EditDetail {
|
||||||
newUsername?: string;
|
newUsername?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|||||||
@@ -15,17 +15,17 @@ import { defineStore } from 'pinia';
|
|||||||
import { SAVE_KEY_NAME } from '@/constants/constants.js';
|
import { SAVE_KEY_NAME } from '@/constants/constants.js';
|
||||||
|
|
||||||
|
|
||||||
interface Position {
|
export interface Position {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Node {
|
export interface Node {
|
||||||
id: string;
|
id: string;
|
||||||
position: Position;
|
position: Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NodePositions {
|
export interface NodePositions {
|
||||||
[direction: string]: {
|
[direction: string]: {
|
||||||
[graphId: string]: Node[];
|
[graphId: string]: Node[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,6 @@ const is_active = computed(() => currentViewingUser.value.is_active);
|
|||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await acctMgmtStore.getUserDetail(currentViewingUser.value.username);
|
await acctMgmtStore.getUserDetail(currentViewingUser.value.username);
|
||||||
visitTime.value = currentViewingUser.value.detail?.visits ?? 0;
|
visitTime.value = currentViewingUser.value.visits ?? 0;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { type BrowserContext } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets authentication cookies to simulate a logged-in user.
|
||||||
|
* MSW handles all API interception via the service worker.
|
||||||
|
* @param context - Playwright browser context.
|
||||||
|
*/
|
||||||
|
export async function loginWithMSW(
|
||||||
|
context: BrowserContext,
|
||||||
|
): Promise<void> {
|
||||||
|
await context.addCookies([
|
||||||
|
{
|
||||||
|
name: "luciaToken",
|
||||||
|
value: "fake-access-token-for-testing",
|
||||||
|
domain: "localhost",
|
||||||
|
path: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "isLuciaLoggedIn",
|
||||||
|
value: "true",
|
||||||
|
domain: "localhost",
|
||||||
|
path: "/",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { defineConfig } from "@playwright/test";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./specs",
|
||||||
|
timeout: 30000,
|
||||||
|
workers: 4,
|
||||||
|
expect: { timeout: 5000 },
|
||||||
|
use: {
|
||||||
|
baseURL: "http://localhost:4173",
|
||||||
|
viewport: { width: 1280, height: 720 },
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { browserName: "chromium" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: {
|
||||||
|
command: "npx vite preview --port 4173",
|
||||||
|
port: 4173,
|
||||||
|
reuseExistingServer: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Account Management", () => {
|
||||||
|
test.beforeEach(async ({ context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays user list on account admin page", async ({ page }) => {
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
// Should display users from fixture
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("Alice Wang")).toBeVisible();
|
||||||
|
await expect(page.getByText("Bob Chen")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows active/inactive status badges", async ({ page }) => {
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
// The user list should show status indicators
|
||||||
|
await expect(page.getByText("testadmin")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigates to my-account page", async ({ page }) => {
|
||||||
|
await page.goto("/my-account");
|
||||||
|
await expect(page).toHaveURL(/\/my-account/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../../helpers";
|
||||||
|
|
||||||
|
const MSG_ACCOUNT_NOT_UNIQUE = "Account has already been registered.";
|
||||||
|
|
||||||
|
test.describe("Account duplication check.", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("When an account already exists, show error message on confirm.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const testAccountName = "000000";
|
||||||
|
|
||||||
|
// First creation: account doesn't exist yet - override via MSW
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = window.__msw__;
|
||||||
|
window.__mswWorker__.use(
|
||||||
|
http.get("/api/users/000000", () =>
|
||||||
|
HttpResponse.json(
|
||||||
|
{ detail: "Not found" },
|
||||||
|
{ status: 404 },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
await page.locator("#input_account_field").fill(testAccountName);
|
||||||
|
await page.locator("#input_name_field").fill(testAccountName);
|
||||||
|
await page.locator("#input_first_pwd").fill(testAccountName);
|
||||||
|
await page
|
||||||
|
.locator(".checkbox-and-text")
|
||||||
|
.first()
|
||||||
|
.locator("div")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeEnabled();
|
||||||
|
await page.getByRole("button", { name: "Confirm" }).click();
|
||||||
|
await expect(page.getByText("Account added")).toBeVisible();
|
||||||
|
|
||||||
|
// Second creation: now account exists - override to return 200 via MSW
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = window.__msw__;
|
||||||
|
window.__mswWorker__.use(
|
||||||
|
http.get("/api/users/000000", () =>
|
||||||
|
HttpResponse.json({
|
||||||
|
username: "000000",
|
||||||
|
name: "000000",
|
||||||
|
is_admin: false,
|
||||||
|
is_active: true,
|
||||||
|
roles: [],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
await page.locator("#input_account_field").fill(testAccountName);
|
||||||
|
await page.locator("#input_name_field").fill(testAccountName);
|
||||||
|
await page.locator("#input_first_pwd").fill(testAccountName);
|
||||||
|
await page
|
||||||
|
.locator(".checkbox-and-text")
|
||||||
|
.first()
|
||||||
|
.locator("div")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeEnabled();
|
||||||
|
await page.getByRole("button", { name: "Confirm" }).click();
|
||||||
|
await expect(page.getByText(MSG_ACCOUNT_NOT_UNIQUE)).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../../helpers";
|
||||||
|
|
||||||
|
test.describe("Password validation on create account.", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("When password is too short, confirm button stays disabled.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
|
||||||
|
await page.locator("#input_account_field").fill("unit-test-0001");
|
||||||
|
await page.locator("#input_name_field").fill("unit-test-0001");
|
||||||
|
// Password shorter than 6 characters
|
||||||
|
await page.locator("#input_first_pwd").fill("aaa");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("When password meets minimum length, confirm button enables.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
|
||||||
|
await page.locator("#input_account_field").fill("unit-test-0001");
|
||||||
|
await page.locator("#input_name_field").fill("unit-test-0001");
|
||||||
|
await page.locator("#input_first_pwd").fill("aaaaaa");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../../helpers";
|
||||||
|
|
||||||
|
test.describe("Create an Account", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
// Override: new usernames should return 404 (account doesn't exist yet)
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = window.__msw__;
|
||||||
|
window.__mswWorker__.use(
|
||||||
|
http.get("/api/users/:username", ({ params }) => {
|
||||||
|
if ((params.username as string).startsWith("unit-test-")) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ detail: "Not found" },
|
||||||
|
{ status: 404 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Create a new account with admin role; should show saved message.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
|
||||||
|
await page.locator("#input_account_field").fill("unit-test-0001");
|
||||||
|
await page.locator("#input_name_field").fill("unit-test-0001");
|
||||||
|
await page.locator("#input_first_pwd").fill("aaaaaa");
|
||||||
|
await page
|
||||||
|
.locator(".checkbox-and-text")
|
||||||
|
.first()
|
||||||
|
.locator("div")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeEnabled();
|
||||||
|
await page.getByRole("button", { name: "Confirm" }).click();
|
||||||
|
await expect(page.getByText("Account added")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Confirm button is disabled when required fields are empty.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
await page.locator("#input_account_field").fill("test");
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../../helpers";
|
||||||
|
|
||||||
|
test.describe("Delete an Account", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Delete button opens confirmation modal and deletes on confirm.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator("img.delete-account").first().click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("ARE YOU SURE TO DELETE"),
|
||||||
|
).toBeVisible();
|
||||||
|
await page.locator("#sure_to_delete_acct_btn").click();
|
||||||
|
await expect(page.getByText("Account deleted")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Cancel button closes the delete confirmation modal.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator("img.delete-account").first().click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("ARE YOU SURE TO DELETE"),
|
||||||
|
).toBeVisible();
|
||||||
|
await page.locator("#calcel_delete_acct_btn").click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("ARE YOU SURE TO DELETE"),
|
||||||
|
).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../../helpers";
|
||||||
|
|
||||||
|
const MODAL_TITLE_ACCOUNT_EDIT = "Account Edit";
|
||||||
|
const MSG_ACCOUNT_EDITED = "Saved";
|
||||||
|
|
||||||
|
test.describe("Edit an account", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Edit an account; modify name and see saved message.", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".btn-edit").first().click();
|
||||||
|
await expect(
|
||||||
|
page.locator("h1", { hasText: MODAL_TITLE_ACCOUNT_EDIT }),
|
||||||
|
).toBeVisible();
|
||||||
|
await page.locator("#input_name_field").clear();
|
||||||
|
await page.locator("#input_name_field").fill("Updated Name");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeEnabled();
|
||||||
|
await page.getByRole("button", { name: "Confirm" }).click();
|
||||||
|
await expect(page.getByText(MSG_ACCOUNT_EDITED)).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Account Management CRUD", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows Create New button", async ({ page }) => {
|
||||||
|
await expect(page.locator("#create_new_acct_btn")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("#create_new_acct_btn"),
|
||||||
|
).toContainText("Create New");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("opens create new account modal", async ({ page }) => {
|
||||||
|
await page.locator("#create_new_acct_btn").click();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("#modal_account_edit_or_create_new"),
|
||||||
|
).toBeVisible();
|
||||||
|
// Should show account, name, password fields
|
||||||
|
await expect(page.locator("#input_account_field")).toBeVisible();
|
||||||
|
await expect(page.locator("#input_name_field")).toBeVisible();
|
||||||
|
await expect(page.locator("#input_first_pwd")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("create account confirm is disabled when fields are empty", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator("#create_new_acct_btn").click();
|
||||||
|
await expect(page.locator(".confirm-btn")).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("create account confirm enables when fields are filled", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator("#create_new_acct_btn").click();
|
||||||
|
await page.locator("#input_account_field").fill("newuser");
|
||||||
|
await page.locator("#input_name_field").fill("New User");
|
||||||
|
await page.locator("#input_first_pwd").fill("password1234");
|
||||||
|
await expect(page.locator(".confirm-btn")).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cancel button closes the modal", async ({ page }) => {
|
||||||
|
await page.locator("#create_new_acct_btn").click();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await page.locator(".cancel-btn").click();
|
||||||
|
await expect(page.locator("#modal_container")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("close (X) button closes the modal", async ({ page }) => {
|
||||||
|
await page.locator("#create_new_acct_btn").click();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await page.locator('img[alt="X"]').click();
|
||||||
|
await expect(page.locator("#modal_container")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("double-click username opens account info modal", async ({ page }) => {
|
||||||
|
// Double-click on the first account username
|
||||||
|
await page.locator(".account-cell").first().dblclick();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delete button opens delete confirmation modal", async ({ page }) => {
|
||||||
|
// Click the delete icon for a non-current user
|
||||||
|
await page.locator(".delete-account").first().click();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await expect(page.locator("#modal_delete_acct_alert")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delete modal has Yes and No buttons", async ({ page }) => {
|
||||||
|
await page.locator(".delete-account").first().click();
|
||||||
|
await expect(page.locator("#calcel_delete_acct_btn")).toBeVisible();
|
||||||
|
await expect(page.locator("#sure_to_delete_acct_btn")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delete modal No button closes the modal", async ({ page }) => {
|
||||||
|
await page.locator(".delete-account").first().click();
|
||||||
|
await page.locator("#calcel_delete_acct_btn").click();
|
||||||
|
await expect(page.locator("#modal_container")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows checkboxes for Set as Admin and Activate in create modal", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator("#create_new_acct_btn").click();
|
||||||
|
await expect(
|
||||||
|
page.locator("#account_create_checkboxes_section"),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Set as admin.")).toBeVisible();
|
||||||
|
await expect(page.getByText("Activate now.")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("search bar filters user list", async ({ page }) => {
|
||||||
|
// Search filters by username, not display name
|
||||||
|
await page.locator("#input_search").fill("user1");
|
||||||
|
await page.locator('img[alt="search"]').click();
|
||||||
|
// Should only show user1 (Alice Wang)
|
||||||
|
await expect(page.getByText("user1")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Account Info Modal", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("double-click username opens info modal with user data", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".account-cell").first().dblclick();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await expect(page.locator("#acct_info_user_name")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("info modal shows Account Information header", async ({ page }) => {
|
||||||
|
await page.locator(".account-cell").first().dblclick();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await expect(page.getByText("Account Information")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("info modal shows account visit info", async ({ page }) => {
|
||||||
|
await page.locator(".account-cell").first().dblclick();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await expect(page.locator("#account_visit_info")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("#account_visit_info"),
|
||||||
|
).toContainText("Account:");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("info modal can be closed via X button", async ({ page }) => {
|
||||||
|
await page.locator(".account-cell").first().dblclick();
|
||||||
|
await expect(page.locator("#modal_container")).toBeVisible();
|
||||||
|
await page.locator('img[alt="X"]').click();
|
||||||
|
await expect(page.locator("#modal_container")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Compare", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await page.locator("li", { hasText: "COMPARE" }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Compare dropdown sorting options", async ({ page }) => {
|
||||||
|
const expectedOptions = [
|
||||||
|
"By File Name (A to Z)",
|
||||||
|
"By File Name (Z to A)",
|
||||||
|
"By Dependency (A to Z)",
|
||||||
|
"By Dependency (Z to A)",
|
||||||
|
"By File Type (A to Z)",
|
||||||
|
"By File Type (Z to A)",
|
||||||
|
"By Last Update (A to Z)",
|
||||||
|
"By Last Update (Z to A)",
|
||||||
|
];
|
||||||
|
|
||||||
|
await page.locator(".p-select").click();
|
||||||
|
const options = page.locator(".p-select-list .p-select-option-label");
|
||||||
|
const count = await options.count();
|
||||||
|
const actualOptions: string[] = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
actualOptions.push((await options.nth(i).textContent()) ?? "");
|
||||||
|
}
|
||||||
|
expect(actualOptions).toEqual(expectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Grid cards are rendered for compare file selection", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const items = page.locator("#compareGridCards li");
|
||||||
|
await expect(items).not.toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Compare button is disabled until two files are dragged", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Compare" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
await page.locator("#compareFile0").dragTo(
|
||||||
|
page.locator("#primaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.locator("#compareFile1").dragTo(
|
||||||
|
page.locator("#secondaryDragCard"),
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Compare" }),
|
||||||
|
).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Enter Compare dashboard and see charts", async ({ page }) => {
|
||||||
|
await page.locator("#compareFile0").dragTo(
|
||||||
|
page.locator("#primaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.locator("#compareFile1").dragTo(
|
||||||
|
page.locator("#secondaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Compare" }).click();
|
||||||
|
await expect(page).toHaveURL(/compare/);
|
||||||
|
|
||||||
|
// Assert chart title spans are visible
|
||||||
|
await expect(
|
||||||
|
page.locator("span", { hasText: "Average Cycle Time" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("span", { hasText: "Cycle Efficiency" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("span", { hasText: "Average Processing Time" }).first(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("span", {
|
||||||
|
hasText: "Average Processing Time by Activity",
|
||||||
|
}),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("span", { hasText: "Average Waiting Time" }).first(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("span", {
|
||||||
|
hasText: "Average Waiting Time between Activity",
|
||||||
|
}),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Compare State button exists on dashboard", async ({ page }) => {
|
||||||
|
await page.locator("#compareFile0").dragTo(
|
||||||
|
page.locator("#primaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.locator("#compareFile1").dragTo(
|
||||||
|
page.locator("#secondaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Compare" }).click();
|
||||||
|
|
||||||
|
await expect(page.locator("#compareState")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Sidebar shows time usage and frequency sections", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator("#compareFile0").dragTo(
|
||||||
|
page.locator("#primaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.locator("#compareFile1").dragTo(
|
||||||
|
page.locator("#secondaryDragCard"),
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Compare" }).click();
|
||||||
|
|
||||||
|
await expect(page.locator("aside")).toBeVisible();
|
||||||
|
const items = page.locator("aside li");
|
||||||
|
await expect(items).not.toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Discover Conformance Page", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/discover/log/297310264/conformance");
|
||||||
|
await expect(
|
||||||
|
page.locator(".p-radiobutton, [class*=conformance]").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page loads and loading overlay disappears", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays Rule Settings sidebar", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Rule Settings")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays Conformance Checking Results heading", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(
|
||||||
|
page.getByText("Conformance Checking Results"),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays rule type radio options", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Have activity")).toBeVisible();
|
||||||
|
await expect(page.getByText("Activity sequence").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("Activity duration")).toBeVisible();
|
||||||
|
await expect(page.getByText("Processing time")).toBeVisible();
|
||||||
|
await expect(page.getByText("Waiting time")).toBeVisible();
|
||||||
|
await expect(page.getByText("Cycle time")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays Clear and Apply buttons", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Clear" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Apply" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays Activity list area", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Activity list")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays default placeholder values in results", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Conformance Rate")).toBeVisible();
|
||||||
|
await expect(page.getByText("Cases").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Discover Map Page", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/discover/log/297310264/map");
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page loads and cytoscape container exists", async ({ page }) => {
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays left sidebar buttons", async ({ page }) => {
|
||||||
|
// Visualization Setting, Filter, Traces buttons
|
||||||
|
await expect(
|
||||||
|
page.locator(".material-symbols-outlined", { hasText: "track_changes" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".material-symbols-outlined", { hasText: "tornado" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".material-symbols-outlined", { hasText: "rebase" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays right sidebar Summary button", async ({ page }) => {
|
||||||
|
await expect(page.locator("#sidebar_state")).toBeVisible();
|
||||||
|
await expect(page.locator("#iconState")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking Visualization Setting button toggles sidebar", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Click the track_changes icon (Visualization Setting)
|
||||||
|
await page
|
||||||
|
.locator("span.material-symbols-outlined", { hasText: "track_changes" })
|
||||||
|
.locator("..")
|
||||||
|
.click();
|
||||||
|
// SidebarView should open
|
||||||
|
await expect(page.getByText("Visualization Setting").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking Summary button toggles sidebar", async ({ page }) => {
|
||||||
|
await page.locator("#iconState").click();
|
||||||
|
// SidebarState should open with insights/stats
|
||||||
|
await expect(page.getByText("Summary").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking Traces button toggles sidebar", async ({ page }) => {
|
||||||
|
await page
|
||||||
|
.locator("span.material-symbols-outlined", { hasText: "rebase" })
|
||||||
|
.locator("..")
|
||||||
|
.click();
|
||||||
|
// SidebarTraces should open
|
||||||
|
await expect(page.getByText("Traces")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Discover Performance Page", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/discover/log/297310264/performance");
|
||||||
|
await expect(
|
||||||
|
page.locator(".chart-container, canvas").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("page loads and loading overlay disappears", async ({ page }) => {
|
||||||
|
// Loading overlay should not be visible after data loads
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays Time Usage sidebar section", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Time Usage").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays Frequency sidebar section", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Frequency").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays sidebar navigation items", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Cycle Time & Efficiency").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("Processing Time").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("Waiting Time").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("Number of Cases").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays chart titles", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Average Cycle Time")).toBeVisible();
|
||||||
|
await expect(page.getByText("Cycle Efficiency")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Average Processing Time").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Average Processing Time by Activity"),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Average Waiting Time").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays frequency chart titles", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("New Cases")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Number of Cases by Activity"),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders canvas elements for charts", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
// Chart.js renders into canvas elements
|
||||||
|
const canvasCount = await page.locator("canvas").count();
|
||||||
|
expect(canvasCount).toBeGreaterThanOrEqual(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sidebar navigation scrolls to section", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
// Click on "Waiting Time" in sidebar
|
||||||
|
await page.locator("li", { hasText: "Waiting Time" }).first().click();
|
||||||
|
// The Waiting Time section should be in view
|
||||||
|
await expect(page.locator("#waitingTime")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Discover Tab Navigation", () => {
|
||||||
|
test.beforeEach(async ({ context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("navigating from Map page", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/discover/log/297310264/map");
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows DISCOVER heading and MAP/CONFORMANCE/PERFORMANCE tabs", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await expect(
|
||||||
|
page.locator("#nav_bar", { hasText: "DISCOVER" }),
|
||||||
|
).toBeVisible();
|
||||||
|
const navItems = page.locator(".nav-item");
|
||||||
|
await expect(navItems).toHaveCount(3);
|
||||||
|
await expect(navItems.nth(0)).toContainText("MAP");
|
||||||
|
await expect(navItems.nth(1)).toContainText("CONFORMANCE");
|
||||||
|
await expect(navItems.nth(2)).toContainText("PERFORMANCE");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking PERFORMANCE tab navigates to performance page", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "PERFORMANCE" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/performance/);
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Time Usage").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking CONFORMANCE tab navigates to conformance page", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "CONFORMANCE" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/conformance/);
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Rule Settings")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows back arrow to return to Files", async ({ page }) => {
|
||||||
|
await expect(page.locator("#backPage")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("#backPage"),
|
||||||
|
).toHaveAttribute("href", "/files");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("navigating from Performance page", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/discover/log/297310264/performance");
|
||||||
|
await expect(
|
||||||
|
page.locator(".chart-container, canvas").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking MAP tab navigates to map page", async ({ page }) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "MAP" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/map/);
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking CONFORMANCE tab navigates to conformance page", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "CONFORMANCE" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/conformance/);
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Rule Settings")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("navigating from Conformance page", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/discover/log/297310264/conformance");
|
||||||
|
await expect(
|
||||||
|
page.locator(".p-radiobutton, [class*=conformance]").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking MAP tab navigates to map page", async ({ page }) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "MAP" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/map/);
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking PERFORMANCE tab navigates to performance page", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "PERFORMANCE" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/performance/);
|
||||||
|
await expect(
|
||||||
|
page.locator(".z-\\[9999\\]"),
|
||||||
|
).not.toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.getByText("Time Usage").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Edge Cases", () => {
|
||||||
|
test.describe("Empty states", () => {
|
||||||
|
test("files page handles empty file list", async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
// Verify page loaded with data first
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
// Now override via MSW and use client-side navigation
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = (window as any).__msw__;
|
||||||
|
(window as any).__mswWorker__.use(
|
||||||
|
http.get("/api/files", () => HttpResponse.json([])),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Trigger re-fetch by navigating via the app's router
|
||||||
|
await page.evaluate(() => {
|
||||||
|
(window as any).__vue_app__?.config?.globalProperties?.$router?.push("/files");
|
||||||
|
});
|
||||||
|
// Wait for re-render
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
// Use a more resilient check: the table exists but no file names
|
||||||
|
await expect(page.locator("table")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("account admin handles empty user list", async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
// Override users endpoint
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = (window as any).__msw__;
|
||||||
|
(window as any).__mswWorker__.use(
|
||||||
|
http.get("/api/users", () => HttpResponse.json([])),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Navigate away and back via app router to trigger re-fetch
|
||||||
|
await page.evaluate(() => {
|
||||||
|
(window as any).__vue_app__?.config?.globalProperties?.$router?.push("/files");
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
(window as any).__vue_app__?.config?.globalProperties?.$router?.push("/account-admin");
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await expect(page.locator("#create_new_acct_btn")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unauthenticated user is redirected to login", async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
// Override my-account to return 401
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = window.__msw__;
|
||||||
|
window.__mswWorker__.use(
|
||||||
|
http.get(
|
||||||
|
"/api/my-account",
|
||||||
|
() => new HttpResponse(null, { status: 401 }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Clear cookies to simulate logged out
|
||||||
|
await context.clearCookies();
|
||||||
|
await page.goto("/files");
|
||||||
|
await expect(page).toHaveURL(/\/login/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unauthenticated user cannot access account-admin", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page).toHaveURL(/\/login/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unauthenticated user cannot access my-account", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/my-account");
|
||||||
|
await expect(page).toHaveURL(/\/login/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Login validation", () => {
|
||||||
|
test("shows error on failed login", async ({ page, context }) => {
|
||||||
|
// Visit login page first to load the app
|
||||||
|
await page.goto("/login");
|
||||||
|
await expect(page.locator("#login_btn_main_btn")).toBeVisible();
|
||||||
|
// Override token endpoint to return 401
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = window.__msw__;
|
||||||
|
window.__mswWorker__.use(
|
||||||
|
http.post("/api/oauth/token", () =>
|
||||||
|
HttpResponse.json(
|
||||||
|
{ detail: "Invalid credentials" },
|
||||||
|
{ status: 401 },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await page.locator("#account").fill("wronguser");
|
||||||
|
await page.locator("#password").fill("wrongpass");
|
||||||
|
await page.locator("#login_btn_main_btn").click();
|
||||||
|
await expect(page).toHaveURL(/\/login/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("confirm stays disabled with only account field filled", async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
await page.locator("#input_account_field").fill("onlyaccount");
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("confirm stays disabled with only name field filled", async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
await page.locator("#input_name_field").fill("onlyname");
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("confirm stays disabled with only password field filled", async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/account-admin");
|
||||||
|
await expect(page.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Create New" }).click();
|
||||||
|
await page.locator("#input_first_pwd").fill("onlypassword");
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Confirm" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("File Operations", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("file list table has sortable columns", async ({ page }) => {
|
||||||
|
// Check that table headers exist with expected columns
|
||||||
|
const table = page.locator("table");
|
||||||
|
await expect(
|
||||||
|
table.locator("th", { hasText: "Name" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
table.locator("th", { hasText: "Dependency" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
table.locator("th", { hasText: "File Type" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
table.locator("th", { hasText: "Owner" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
table.locator("th", { hasText: "Last Update" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking column header sorts the table", async ({ page }) => {
|
||||||
|
// Click "Name" header to sort
|
||||||
|
await page.locator("th", { hasText: "Name" }).click();
|
||||||
|
// After sorting, table should still have data
|
||||||
|
const rows = page.locator("table tbody tr");
|
||||||
|
await expect(rows).not.toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("table rows show file data from fixture", async ({ page }) => {
|
||||||
|
const tbody = page.locator("table tbody");
|
||||||
|
await expect(
|
||||||
|
tbody.getByText("sample-process.xes").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(tbody.getByText("filtered-sample").first()).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
tbody.getByText("production-log.csv").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("table shows owner names", async ({ page }) => {
|
||||||
|
const tbody = page.locator("table tbody");
|
||||||
|
await expect(tbody.getByText("Test Admin").first()).toBeVisible();
|
||||||
|
await expect(tbody.getByText("Alice Wang")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("table shows file types", async ({ page }) => {
|
||||||
|
const tbody = page.locator("table tbody");
|
||||||
|
await expect(tbody.getByText("log").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("right-click on file row shows context menu", async ({ page }) => {
|
||||||
|
// PrimeVue DataTable with contextmenu
|
||||||
|
await page.locator("table tbody tr").first().click({ button: "right" });
|
||||||
|
// Context menu behavior depends on implementation
|
||||||
|
// Just verify the right-click doesn't break anything
|
||||||
|
const rows = page.locator("table tbody tr");
|
||||||
|
await expect(rows).not.toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("grid view shows file cards", async ({ page }) => {
|
||||||
|
// Switch to grid view
|
||||||
|
await page.locator("li.cursor-pointer").last().click();
|
||||||
|
// Grid cards should be visible
|
||||||
|
const cards = page.locator("li[title]");
|
||||||
|
await expect(cards).not.toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Import button opens upload modal", async ({ page }) => {
|
||||||
|
await page.locator("#import_btn").click();
|
||||||
|
// Upload modal should appear
|
||||||
|
await expect(page.locator("#import_btn")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Files Page", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays the file list after login", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("h2", { hasText: "All Files" }),
|
||||||
|
).toBeVisible();
|
||||||
|
// Should display file names from fixture
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("filtered-sample").first()).toBeVisible();
|
||||||
|
await expect(page.getByText("production-log.csv").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows Recently Used section", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("h2", { hasText: "Recently Used" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("switches to DISCOVER tab", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await page.locator(".nav-item", { hasText: "DISCOVER" }).click();
|
||||||
|
// DISCOVER tab shows filtered file types
|
||||||
|
await expect(
|
||||||
|
page.locator("h2", { hasText: "All Files" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("switches to COMPARE tab and shows drag zones", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await page.locator(".nav-item", { hasText: "COMPARE" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Performance Comparison"),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Drag and drop a file here").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows Import button on FILES tab", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
await expect(page.locator("#import_btn")).toContainText("Import");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can switch between list and grid view", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
// DataTable (list view) should be visible by default
|
||||||
|
await expect(page.locator("table")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("double-click file navigates to discover page", async ({ page }) => {
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
// Double-click the first file row in the table
|
||||||
|
// The actual route depends on file type (log->map, log-check->conformance, etc.)
|
||||||
|
await page.locator("table tbody tr").first().dblclick();
|
||||||
|
await expect(page).toHaveURL(/\/discover/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Files Page - COMPARE Tab", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
// Switch to COMPARE tab
|
||||||
|
await page.locator("li", { hasText: "COMPARE" }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows Performance Comparison heading", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator("h2", { hasText: "Performance Comparison" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows two drag-and-drop slots", async ({ page }) => {
|
||||||
|
await expect(page.locator("#primaryDragCard")).toBeVisible();
|
||||||
|
await expect(page.locator("#secondaryDragCard")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drag slots show placeholder text", async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.locator("#primaryDragCard"),
|
||||||
|
).toContainText("Drag and drop a file here");
|
||||||
|
await expect(
|
||||||
|
page.locator("#secondaryDragCard"),
|
||||||
|
).toContainText("Drag and drop a file here");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Compare button is disabled when no files are dragged", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Compare" }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows sorting dropdown", async ({ page }) => {
|
||||||
|
await expect(page.locator(".p-select")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("grid cards display file names", async ({ page }) => {
|
||||||
|
await expect(page.locator("#compareGridCards")).toBeVisible();
|
||||||
|
const items = page.locator("#compareGridCards li");
|
||||||
|
await expect(items).not.toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking sorting dropdown shows sort options", async ({ page }) => {
|
||||||
|
await page.locator(".p-select").click();
|
||||||
|
await expect(page.locator(".p-select-list")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".p-select-option", { hasText: "By File Name" }).first(),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { loginWithMSW } from "../helpers";
|
||||||
|
|
||||||
|
test.describe("Files to Discover Entry Flow", () => {
|
||||||
|
test.beforeEach(async ({ page, context }) => {
|
||||||
|
await loginWithMSW(context);
|
||||||
|
await page.goto("/files");
|
||||||
|
await expect(page.getByText("sample-process.xes").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("double-click table row to enter Discover", () => {
|
||||||
|
test("double-click log file navigates to Map page", async ({ page }) => {
|
||||||
|
// Target the Name column (has class .fileName) to avoid matching Dependency column
|
||||||
|
await page
|
||||||
|
.locator("td.fileName", { hasText: "sample-process.xes" })
|
||||||
|
.locator("..")
|
||||||
|
.dblclick();
|
||||||
|
await expect(page).toHaveURL(/\/discover\/log\/1\/map/);
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("double-click filter file navigates to Map page", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page
|
||||||
|
.locator("td.fileName", { hasText: "filtered-sample" })
|
||||||
|
.locator("..")
|
||||||
|
.dblclick();
|
||||||
|
await expect(page).toHaveURL(/\/discover\/filter\/10\/map/);
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("double-click to enter Discover from file list", () => {
|
||||||
|
test("double-click log file navigates to Map page", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Find the row with "production-log.csv" (a pure log, no parent ambiguity)
|
||||||
|
await page
|
||||||
|
.locator("table tbody tr", { hasText: "production-log.csv" })
|
||||||
|
.first()
|
||||||
|
.dblclick();
|
||||||
|
await expect(page).toHaveURL(/\/discover\/log\/.*\/map/);
|
||||||
|
await expect(page.locator("#cy")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("DISCOVER tab filters files", () => {
|
||||||
|
test("clicking DISCOVER tab shows only Log, Filter, and Rule files", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.locator(".nav-item", { hasText: "DISCOVER" }).click();
|
||||||
|
await expect(
|
||||||
|
page.locator("td.fileName", { hasText: "sample-process.xes" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("td.fileName", { hasText: "filtered-sample" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("td.fileName", { hasText: "conformance-check-1" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Navbar state after entering Discover", () => {
|
||||||
|
test("shows DISCOVER heading and tabs after entering from Files", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page
|
||||||
|
.locator("td.fileName", { hasText: "sample-process.xes" })
|
||||||
|
.locator("..")
|
||||||
|
.dblclick();
|
||||||
|
await expect(page).toHaveURL(/\/discover\//);
|
||||||
|
await expect(
|
||||||
|
page.locator("#nav_bar", { hasText: "DISCOVER" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".nav-item", { hasText: "MAP" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".nav-item", { hasText: "CONFORMANCE" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(".nav-item", { hasText: "PERFORMANCE" }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows back arrow pointing to /files", async ({ page }) => {
|
||||||
|
await page
|
||||||
|
.locator("td.fileName", { hasText: "sample-process.xes" })
|
||||||
|
.locator("..")
|
||||||
|
.dblclick();
|
||||||
|
await expect(page).toHaveURL(/\/discover\//);
|
||||||
|
await expect(
|
||||||
|
page.locator("#backPage"),
|
||||||
|
).toHaveAttribute("href", "/files");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/03/22
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.describe("Login Flow", () => {
|
||||||
|
test("renders the login form", async ({ page }) => {
|
||||||
|
await page.goto("/login");
|
||||||
|
await expect(page.getByRole("heading", { name: "LOGIN", exact: true }).first()).toBeVisible();
|
||||||
|
await expect(page.locator("#account")).toBeVisible();
|
||||||
|
await expect(page.locator("#password")).toBeVisible();
|
||||||
|
await expect(page.locator("#login_btn_main_btn")).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("login button is disabled when fields are empty", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/login");
|
||||||
|
await expect(page.locator("#login_btn_main_btn")).toBeDisabled();
|
||||||
|
|
||||||
|
// Only username filled - still disabled
|
||||||
|
await page.locator("#account").fill("testuser");
|
||||||
|
await expect(page.locator("#login_btn_main_btn")).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("login button enables when both fields are filled", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/login");
|
||||||
|
await page.locator("#account").fill("testadmin");
|
||||||
|
await page.locator("#password").fill("password123");
|
||||||
|
await expect(page.locator("#login_btn_main_btn")).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("successful login redirects to /files", async ({ page }) => {
|
||||||
|
await page.goto("/login");
|
||||||
|
await page.locator("#account").fill("testadmin");
|
||||||
|
await page.locator("#password").fill("password123");
|
||||||
|
await page.locator("#login_btn_main_btn").click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/files/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("failed login shows error message", async ({ page }) => {
|
||||||
|
// Visit login first to load app + MSW
|
||||||
|
await page.goto("/login");
|
||||||
|
await expect(page.locator("#login_btn_main_btn")).toBeVisible();
|
||||||
|
// Override the token endpoint to return 401 via MSW
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const { http, HttpResponse } = window.__msw__;
|
||||||
|
window.__mswWorker__.use(
|
||||||
|
http.post("/api/oauth/token", () =>
|
||||||
|
HttpResponse.json(
|
||||||
|
{ detail: "Incorrect username or password" },
|
||||||
|
{ status: 401 },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.locator("#account").fill("wronguser");
|
||||||
|
await page.locator("#password").fill("wrongpass");
|
||||||
|
await page.locator("#login_btn_main_btn").click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByText("Incorrect account or password"),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("toggles password visibility", async ({ page }) => {
|
||||||
|
await page.goto("/login");
|
||||||
|
await page.locator("#password").fill("secret123");
|
||||||
|
await expect(
|
||||||
|
page.locator("#password"),
|
||||||
|
).toHaveAttribute("type", "password");
|
||||||
|
|
||||||
|
// Click the eye icon to show password
|
||||||
|
await page.locator('label[for="passwordt"] span.cursor-pointer').click();
|
||||||
|
await expect(
|
||||||
|
page.locator("#password"),
|
||||||
|
).toHaveAttribute("type", "text");
|
||||||
|
|
||||||
|
// Click again to hide
|
||||||
|
await page.locator('label[for="passwordt"] span.cursor-pointer').click();
|
||||||
|
await expect(
|
||||||
|
page.locator("#password"),
|
||||||
|
).toHaveAttribute("type", "password");
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user