The GitHub Actions job "Required Checks" on texera.git/main has succeeded. Run started by GitHub user github-merge-queue[bot] (triggered by github-merge-queue[bot]).
Head commit for run: 7d21555bceec552a307c0d4572ff0ee8cd477f94 / Matthew B. <[email protected]> feat(dashboard): add custom cover image for workflow cards (#5704) ### What changes were proposed in this PR? Adds an optional, per-workflow custom cover image for dashboard workflow cards, persisted on the backend so the cover syncs across sessions and browsers for anyone with access to the workflow. - Add a `workflow_cover_image(wid, image)` table (`wid` PK, FK to `workflow` with `ON DELETE CASCADE`; the cover is stored as a downscaled JPEG data URL) to `sql/texera_ddl.sql`, plus a `sql/updates/27.sql` migration registered as Liquibase changeSet `27`. - Add access-gated `GET`/`PUT`/`DELETE /workflow/{wid}/cover` endpoints on `WorkflowResource`: - `GET` requires read access (403 otherwise) and returns the stored data URL, or 404 when no cover is set. - `PUT` requires write access (403 otherwise), rejects an empty/blank body (400), requires the value to be a `data:image/...` data URL (400 otherwise), and enforces a 4 MB size cap (400 otherwise). Writes are an upsert keyed on `wid`. - `DELETE` requires write access and is idempotent. - Set/delete authorize on `hasWriteAccess` (consistent with `makePublic` / `updateWorkflowName` / `updateWorkflowDescription`), so any WRITE collaborator can change the shared per-workflow cover; the owner-only UI is where the control is surfaced, not a security boundary. - Add `WorkflowCoverService` (frontend): `getCover` (maps a 404 to `undefined`), `setCoverFromFile` (downscales the chosen image to a 640px longest edge and re-encodes it as a JPEG data URL at 0.8 quality before uploading), and `clearCover`. - Add hover-revealed controls on the workflow card: a camera button to pick an image and a rollback button to reset, shown only to the owner in private search (`canEditCover`). `previewImage` prefers the stored cover and falls back to the default card background. Note on validation: the setter currently accepts any `data:image/...` data URL (it does not enforce a raster-only MIME allowlist), so `data:image/svg+xml;...` and non-base64 forms are accepted. This is not a live XSS vector today because covers are rendered via `<img src>`, which does not execute embedded SVG scripting. Tightening this to a raster allowlist is a possible follow-up. ### Any related issues, documentation, discussions? Closes: #5703 Follow-up: #5920 tracks folding a `hasCover` flag into the workflow list/search payload so cover-less cards issue no per-card cover request (avoids the current per-card `GET /{wid}/cover` fan-out). ### How was this PR tested? - Backend: `WorkflowResourceCoverSpec` (uses the `MockTexeraDB` embedded-Postgres harness) covers all three endpoints: 404 when unset, set/get round-trip, read by a user with read access, forbidden read/write for a user without access, upsert replace, empty/blank/null and non-`data:image/` validation rejections, size-limit rejection, no-persist on validation failure, and idempotent delete. Run `sbt "WorkflowExecutionService/testOnly *WorkflowResourceCoverSpec"`. - Frontend (service): `workflow-cover.service.spec.ts` covers `getCover` mapping the image, 404 resolving to `undefined`, `clearCover` issuing a DELETE, `setCoverFromFile` PUTting the resized data URL, and `fileToResizedDataUrl` itself (downscale math, canvas-context failure, undecodable image, and file-read failure). Run `cd frontend && yarn test --include='**/workflow-cover.service.spec.ts' --watch=false`. - Frontend (component): `card-item.component.spec.ts` covers the cover behaviors (owner-in-private-search gating, cover load on init, default-image fallback, upload success / non-image rejection / upload failure, reset success / failure, `openImagePicker`, and the no-file / non-numeric-id early returns). Run `cd frontend && yarn test --include='**/card-item.component.spec.ts' --watch=false`. - Manual: open the dashboard in private search as a workflow owner, hover a card, click the camera icon, pick an image, and expect the card preview to update; reload in another browser and expect the same cover (synced via the backend). - Manual: click the rollback icon and expect the card to revert to the default background, with the cover gone after reload. <img width="424" height="530" alt="image" src="https://github.com/user-attachments/assets/99f11d99-0bcc-43c0-ad93-bde4457a127a" /> <img width="424" height="530" alt="image" src="https://github.com/user-attachments/assets/553c4be5-0593-43f6-af92-f6260dc80d43" /> ### Was this PR authored or co-authored using generative AI tooling? Co-authored with Claude Opus 4.8 in compliance with ASF --------- Co-authored-by: Xuan Gu <[email protected]> Report URL: https://github.com/apache/texera/actions/runs/28732415168 With regards, GitHub Actions via GitBox
